文档章节

设计模式之适配器模式

AbeJeffrey
 AbeJeffrey
发布于 2017/05/05 21:58
字数 2441
阅读 75
收藏 0

场景介绍:厂商A生产的电源设备是普通的扁平两项插头,而厂商B生产的电源设备则是两项圆头的插头,客户手机采用的厂商A的电源设备充电,若当前客户只有一个厂商B的电源设备,如何给手机充电呢?提供一个电源适配器就好了。

概念

适配器模式的意图在于将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适配器模式是一种又叫包装器,Wrapper。

参与角色

Target:定义Client使用的与特定领域相关的接口;

Client:Target接口的最终使用者;

Adaptee:一个已经存在的接口,这个接口需要适配;

Adapter:适配角色,对Adaptee接口和Target接口进行适配。

场景分析

定义一个接口PowerTarget表示厂商A生产的电源设备,接口PowerAdaptee表示厂商B生产的电源设备,PowerAdapter就是我们要实现的适配器,由PowerAdapter来完成PowerTarget接口和PowerAdaptee接口的适配,通常有这种方式来实现适配器功能:1 实现PowerTarget接口和继承PowerAdaptee接口的实现 2  实现PowerTarget接口和将PowerTarget接口的实例作为PowerAdapter的组成部分。两种方法对应了Adapter模式的类和对象版本。

类图结构

类适配器使用实现目标接口和泛化另一个接口的实现进行适配,对应了C++中的多继承。类图如下:

对象适配器使用实现目标接口和组合另一个接口的实现进行适配,类图如下:

比较两种适配器

类适配器
• 用一个具体的Adapter类对Adaptee和Target进行匹配,当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作。
• 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。
• 仅仅引入了一个对象,并不需要额外的指针以间接得到Adaptee。
对象适配器
• 允许一个Adapter与多个Adaptee—即Adaptee本身以及它的所有子类(如果有子类的话)同时工作。Adapter也可以一次给所有的Adaptee添加功能。
• 使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。

实现

类适配器实现:

public interface PowerTarget {
    /**
     * 充电操作
     */
    public void charge();
}
public interface PowerAdaptee {
    /**
     * 个性充电
     */
    public void specalCharge();
}
public class PowerAdapteeImpl implements PowerAdaptee {
    @Override
    public void specalCharge() {
        System.out.println("------specal charge---------");
    }
}
public class PowerAdapter extends PowerAdapteeImpl implements PowerTarget{
    @Override
    public void charge() {
        super.specalCharge();
    }

}
public class Client {
    public static void main(String[] args) {
        PowerTarget target=new PowerAdapter();
        target.charge();
    }
}

对象适配器:

public class ObjectPowerAdapter implements PowerTarget{
    private PowerAdaptee adaptee;
    public ObjectPowerAdapter(PowerAdaptee adaptee){
        this.adaptee=adaptee;
    }
    @Override
    public void charge() {
        adaptee.specalCharge();
    }
}
public class Client {
    public static void main(String[] args) {
        PowerTarget target=new ObjectPowerAdapter(new PowerAdapteeImpl());
        target.charge();
    }
}

Adapter使用场景

• 想使用一个已经存在的类,而它的接口不符合使用需求。

• 想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。

• 想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。可使用对象适配器适配它的父类接口(此场景仅适用于对象adapter)。

使用Adapter的一些考量:

(1) Adapter的匹配程度,Adapter适配Adaptee和Target的工作量取决于Adaptee接口和Target接口的相似程度。

(2) 双向适配,由于Adapter本身不兼容Adaptee接口,因此在使用Adaptee对象地方想要使用Adapter则不一定有效。这时可以考虑使用双向适配器,双向适配器对所有用户都透明。Adapter同时实现Target和Adaptee对于的接口即可实现双向适配器,简单看看双向适配器的类图:

双向适配器的代码如下:

public class DoublePowerAdapter implements PowerTarget, PowerAdaptee {
    private PowerTarget  target;
    private PowerAdaptee adaptee;

    public DoublePowerAdapter(PowerTarget target) {
        this.target = target;
    }

    public DoublePowerAdapter(PowerAdaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void specalCharge() {
        if (null != target) {
            target.charge();
        }
    }

    @Override
    public void charge() {
        if (null != adaptee) {
            adaptee.specalCharge();
        }
    }

}

(3) 可插入的Adapter,当其他的类使用一个类时,如果所需的假定条件越少,这个类就更具可复用性。如果将接口匹配构建为一个类,就不需要假定对其他的类可见的是一个相同的接口。也就是说,接口匹配使得我们可以将自己的类加入到一些现有的系统中去,而这些系统对这个类的接口可能会有所不同。(这段文字很隐晦啊,读半天没读懂)。使用pluggable adapter描述那些具有内部接口适配的类。下面结合设计模式一书中的示例详细分析pluggable adapter的实现。

可插入的Adapter实现

场景:TreeDisplay表示一个窗口组件,它可以图形化显示树状结构。如果这是一个具有特殊用途的窗口组件,仅在一个应用中使用,我们可能要求它所显示的对象有一个特殊的接口,即它们都是抽象类Tree的子类。如果我们希望使TreeDisplay有具有良好的复用性的话(比如说,我们希望将它作为可用窗口组件工具箱的一部分),那么这种要求将是不合理的。应用程序可以自定义树结构类,而不应一定要使用我们的抽象类Tree。不同的树结构会有不同的接口。

如,在一个目录层次结构中,可以通过GetSubdirectories操作进行访问子目录,然而在一个继承式层次结构中,相应的操作可能被称为GetSubclasses。尽管这两种层次结构使用的接口不同,一个可复用的TreeDisplay窗口组件必须能显示所有这两种结构。也就是说,TreeDisplay应具有接口适配的功能。

要实现可插入的Adapter,首先需要为Adaptee 找到一个“窄”接口,即可用于适配的最小操作集。因为包含较少操作的窄接口相对包含较多操作的宽接口比较容易进行匹配。对于TreeDisplay而言,被匹配的对象可以是任何一个层次式结构。因此最小接口集合仅包含两个操作:一个操作定义如何在层次结构中表示一个节点,另一个操作返回该节点的子节点。下面针对该应用提供两种实现方式:

方案1:使用抽象操作

这里需要将TreeDisplay定义为抽象类,用DirectoryTreeDisplay作为目录层次结构类FileSystemEntity的适配器,InheritTreeDisplay作为继承式层次结构类InheritEntity的适配器。下面看类图:

方案2:使用代理对象

该实现需要将TreeDisplay访问树结构的请求转发给代理对象,该代理对象被抽象为接口TreeAccessorDelegate,各层次结构的适配器分别实现接口TreeAccessorDelegate,下面看类图:

角色说明:

Client:TreeDisplay

Target:TreeAccessorDelegate

Adapter:DirectoryTreeDisplay,InheritTreeDisplay

Adaptee:FileSystemEntity,InheritEntity

这里仅提供使用代理对象实现可插入的Adapter的简单源码,使用抽象操作实现的方式读者可自行实现。

/**
 * 
 * TODO Target 
 * @className: TreeAccessorDelegate.java
 * @author AbeJeffrey
 * @date 2017年5月5日 下午9:40:50
 */
public interface TreeAccessorDelegate {
    public List<Node> getChildren(Node node);
}
/**
 * 
 * TODO Adapter 
 * @className: DirectoryTreeDisplay.java
 * @author AbeJeffrey
 * @date 2017年5月5日 下午9:42:04
 */
public class DirectoryTreeDisplay implements TreeAccessorDelegate {
    private FileSystemEntity    entity;
    private static final String PATH = "E:/java";

    public DirectoryTreeDisplay(FileSystemEntity entity) {
        this.entity = entity;
    }

    @Override
    public List<Node> getChildren(Node node) {
        if (null != node) {
            List<Node> list = Lists.newArrayList();
            File file=new File(node.getValue());
            FileNode fileNode = entity.getNode(file);
            if (null != fileNode) {
                for (FileNode tempNode : entity.getSubdirectories(file)) {
                    list.add(Trees.newNode(PATH + "/" + tempNode.getValue().getName()));
                }
            }
            return list;
        }
        return null;
    }
}
/**
 * 
 * TODO Adaptee 
 * @className: FileSystemEntity.java
 * @author AbeJeffrey
 * @date 2017年5月5日 下午9:45:55
 */
public class FileSystemEntity {
    private DirTree tree;
    public static final String PATH="E:/java";
    private static final String FILE_NAME="java";
    public FileSystemEntity(){
        buildTree(new File(PATH));
    }
    /*public static void main(String[] args) {
        File filepath = new File(PATH); 
        FileSystemEntity entity=new FileSystemEntity();
        entity.buildTree(filepath);
        entity.getTree().display(entity.getTree().getNode(filepath));
        entity.getTree().display(entity.getTree().getNode(new File("E:/java/dump")));
    }*/
    
    public void buildTree(File files) {
        if (files.list() == null) {
            return;
        }else{
            if(files.getName().equals(FILE_NAME)){
                tree=Trees.newDirTree();
                tree.addNode(null,files);
            }
            String[] filelist = files.list();  
            for (int i =0; i<filelist.length; i++) {
                File newfile = new File(files+"/"+filelist[i]);
                tree.addNode(tree.getNode(files),newfile);
                buildTree(newfile);
            }
        }
    }
    public FileNode getNode(File file){
        return this.tree.getNode(file);
    }
    public List<FileNode> getSubdirectories(File file){
        return this.tree.getNode(file).getChildlist();
    }
}
/**
 * 
 * TODO Client 
 * @className: TreeDisplay.java
 * @author AbeJeffrey
 * @date 2017年5月5日 下午9:45:19
 */
public class TreeDisplay {
    private TreeAccessorDelegate delegate;
    private Tree                 tree;
    private static final String  PATH = "E:/java";
    public static void main(String[] args) {
        TreeDisplay dispaly = new TreeDisplay(new DirectoryTreeDisplay(new FileSystemEntity()));
        dispaly.buildTree(new Node(PATH));
        dispaly.display(dispaly.tree.getNode(PATH));
    }

    public TreeDisplay(TreeAccessorDelegate delegate) {
        this.delegate = delegate;
    }
    public void display(Node node) {
        if (null != tree) {
            tree.display(node);
        }
    }
    public void buildTree(Node node) {
        if (null == node) {
            return;
        }
        if (null == tree) {
            tree = Trees.newTree();
        }
        List<Node> List = delegate.getChildren(node);
        if (PATH.equals(node.getValue())) {
            tree.addNode(null, node.getValue());
        }
        for (Node n : List) {
            tree.addNode(tree.getNode(node.getValue()), n.getValue());
            buildTree(n);
        }
    }
}

该示例代码仅用于帮助理清思路,若有疑问,可联系笔者。

总结,使用可插入的Adapter,客户端可灵活地将一些不兼容的类加入的现有系统中,通过使用代理来实现可变的操作,可保证客户端的透明化。

本文参考文献—设计模式:可复用面向对象软件的基础。

欢迎指出本文有误的地方,若对您有帮助,点个赞支持一下呗!转载请注明原文出处

https://my.oschina.net/7001/blog/893636

© 著作权归作者所有

AbeJeffrey
粉丝 39
博文 43
码字总数 116095
作品 0
杭州
高级程序员
私信 提问

暂无文章

OSChina 周日乱弹 —— 程序员做噩梦

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @-冰冰棒- :#今日歌曲推荐# 手嶌葵《Kiss The Girl》 《Kiss The Girl》- 手嶌葵 手机党少年们想听歌,请使劲儿戳(这里) @Sharon啊 :今天...

小小编辑
34分钟前
77
5
Another app is currently holding the yum lock; waiting for it to exit...

Another app is currently holding the yum lock; waiting for it to exit... The other application is: PackageKit Memory : 153 M RSS (266 MB VSZ) Started: Thu Jul 12 00:03......

圣洁之子
42分钟前
2
0
FastDateFormat 研究

FastDateFormat 对缓存的利用,其实就是用ConcurrentHashMap 做了一个map类型的缓存 public F getInstance(final String pattern, TimeZone timeZone, Locale locale) { Validate......

暗中观察
今天
3
0
Android双向绑定原理简述

Android双向绑定原理简述 双向绑定涉及两个部分,即将业务状态的变化传递给UI,以及将用户输入信息传递给业务模型。 首先我们来看业务状态是如何传递给UI的。开启dataBinding后,编译器为布局...

tommwq
今天
4
0
Spring系列教程八: Spring实现事务的两种方式

一、 Spring事务概念: 事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。...

我叫小糖主
今天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部