Chapter Index
OG

適配器模式和裝飾者模式

適配器模式和裝飾者模式

适配器模式

网上有很多适配器模式的定义和讲解,这里我就记录下自己对适配器模式的理解,更多的大家可以在网上看。

适配器模式到底是什么,也就是所谓的定义:

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。

这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。

1、先创建一个SD卡的接口:

public interface SDCard {
    //读取SD卡方法
    String readSD();

    //写入SD卡功能
    int writeSD(String msg);
}

2、创建SD卡接口的实现类,模拟SD卡的功能:

public class SDCardImpl implements SDCard {
    @Override
    public String readSD() {
        String msg = "sdcard read a msg :hello word SD";
        return msg;
    }

    @Override
    public int writeSD(String msg) {
        System.out.println("sd card write msg : " + msg);
        return 1;
    }
}

3、创建计算机接口,计算机提供读取SD卡方法:

public interface Computer {
    String readSD(SDCard sdCard);
}

4、创建一个计算机实例,实现计算机接口,并实现其读取SD卡方法:

public class ThinkpadComputer implements Computer {
    @Override
    public String readSD(SDCard sdCard) {
        if (sdCard == null) throw new NullPointerException("sd card null");
        return sdCard.readSD();
    }
}

5、这时候就可以模拟计算机读取SD卡功能:

public class ComputerReadDemo {
    public static void main(String[] args) {
        Computer computer = new ThinkpadComputer();
        SDCard sdCard = new SDCardImpl();
        System.out.println(computer.readSD(sdCard));
    }
}

二、接下来在不改变计算机读取SD卡接口的情况下,通过适配器模式读取TF卡:

1、创建TF卡接口:

public interface TFCard {
    String readTF();

    int writeTF(String msg);
}

2、创建TF卡实例:

public class TFCardImpl implements TFCard {
    @Override
    public String readTF() {
        String msg = "tf card reade msg : hello word tf card";
        return msg;
    }

    @Override
    public int writeTF(String msg) {
        System.out.println("tf card write a msg : " + msg);
        return 1;
    }
}

3、创建SD适配TF (也可以说是SD兼容TF,相当于读卡器):

实现SDCard接口,并将要适配的对象作为适配器的属性引入。

public class SDAdapterTF implements SDCard {
    private TFCard tfCard;

    public SDAdapterTF(TFCard tfCard) {
        this.tfCard = tfCard;
    }

    @Override
    public String readSD() {
        System.out.println("adapter read tf card ");
        return tfCard.readTF();
    }

    @Override
    public int writeSD(String msg) {
        System.out.println("adapter write tf card");
        return tfCard.writeTF(msg);
    }
}

4、通过上面的例子测试计算机通过SD读卡器读取TF卡:

public class ComputerReadDemo {
    public static void main(String[] args) {
        Computer computer = new ThinkpadComputer();
        SDCard sdCard = new SDCardImpl();
        System.out.println(computer.readSD(sdCard));
        System.out.println("====================================");
        TFCard tfCard = new TFCardImpl();
        SDCard tfCardAdapterSD = new SDAdapterTF(tfCard);
        System.out.println(computer.readSD(tfCardAdapterSD));
    }
}

输出:

sdcard read a msg :hello word SD ==================================== adapter read tf card tf card reade msg : hello word tf card

在这种模式下,计算机并不需要知道具体是什么卡,只需要负责操作接口即可,具体操作的什么类,由适配器决定。

可能这个例子不太好,但是也刚好我们理解一下适配器模式吧,将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

应用场景:

主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。核心思想:适配器继承或依赖已有的对象,实现想要的目标接口。

优点:

1、可以让任何两个没有关联的类一起运行。

2、提高了类的复用。

3、增加了类的透明度。

4、灵活性好。

缺点:

1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

装饰者模式

装饰装饰当然是一个东西还能用,但是呢达不到理想的效果想要更好一点。所以就在原来的基础上进行装饰从而达到目的,这就是装饰者模式啦,和适配器区别在于,适配器模式是原来的东西在现在的环境不能使用,为了达到使用的效果需要使用适配器才能使用。而装饰者模式是原来的东西在现在的环境还能用,但是呢,你想新增一些特性,所以就需要对原来的东西进行装饰,达到想要的效果。

装饰者模式:就是动态地把职责附加到已有对象上去,实现功能扩展。这种特性,使得装饰者模式提供了比继承更具有弹性的解决方案。

下面举一个比较通俗的例子:

送你一个女朋友怎么样!想她是美国金发大妞?浪漫的法国女郎?国产的萌萌哒妹子?OK,没问题!你想她是哪个国家的就是哪个国家的。她们有不同的爱好或者习惯,每一个这样的女孩,都可以看作是一个 Java 类。我知道此刻你一定在想,这一个、那一个...那岂不是有很多类?这种方式没有扩展性,每当有一个新类型的女孩,就得又新建一个类,这简直就是类爆炸啊!

所以这个时候就可以用到装饰者模式,先讲一些共有的特性提取出来形成积累,然后需要一个装饰者的抽象类,来继承基类,其他的不同特性每个特性都自成一个类来继承这个装饰者的类。从而达到通过不同的装饰达到不用的效果,就像房子通过不同的装修达到不同的效果。

下面来看下代码:

整体的目录:


Girl.java

package cn.zlf.code.decoratormode;
/**
 * 抽象类 girl ,作为基类
 */
public abstract class Girl {
    String description;
    public String getDescription() {
        return description;
    }
}


EnGirl.java



package cn.zlf.code.decoratormode;
/**
 * 外国女孩的类
 */
public class EnGirl extends Girl{
    private static final String ENGIRL="我是外国女孩,";
    public  EnGirl(){
        description=ENGIRL;
    }
}


CnGirl.java



package cn.zlf.code.decoratormode;
/**
 *国产妹子
 */
public class CnGirl extends Girl{
    private static final String CNGIRL="我是国产妹子,";
    public  CnGirl(){
        description=CNGIRL;
    }
}


GirlDecorator.java



package cn.zlf.code.decoratormode;
/**
 * 装饰者
 */
public abstract class GirlDecorator extends Girl{
    public abstract String getDescription();
}


/**
 * 好了,接下来我们想要什么样的女朋友呢
 * 比如说:国产黑色长发妹子。
 * 那我们现在该怎样使用这个装饰者呢
 * 需要一个BlackLongHairGirl 来集成这个装饰者
 */


BlackLongHairGirl.java



package cn.zlf.code.decoratormode;
/**
 * 国产黑色长发妹子
 */
public class BlackLongHairGirl extends GirlDecorator{
    private  Girl girl;
    private static final String BLACKLongHair="黑色长发,";
    public BlackLongHairGirl(Girl girl){
        this.girl=girl;
    }
    @Override
    public String getDescription() {
        return girl.getDescription()+BLACKLongHair;
    }
}
/**
 * 上面黑色和长发按正常的实际上是不能放一起的,毕竟黑色的一定是长发。
 * 所以在实际开发时
 * 要注意充分考虑代码的可重用性和拓展性
 * 这里就暂时不处理了,我们再写一个类来表示一个新特征
 * 比如说想要一个黑色长发大长腿的女朋友
 * 现在已经有黑色长发了
 * 所以还需要一个大长腿
 * 所以需要创建一个BigLongLegsGirl
 */


BigLongLegsGirl.java



package cn.zlf.code.decoratormode;
/**
 * 大长腿女孩
 * 继承装饰者,这里为什么不直接继承BlackLongHairGirl呢
 * 就是考虑代码的拓展性,万一下次想要个短发长腿的不就炸了
 * 相当于把不同的属性彻底分开降低耦合
 */
public class BigLongLegsGirl extends GirlDecorator{
    private  Girl girl;
    private static final String BIGLONGLEGS="大长腿,";
    public BigLongLegsGirl(Girl girl){
        this.girl=girl;
    }
    @Override
    public String getDescription() {
        return girl.getDescription()+BIGLONGLEGS;
    }
}
/**
 * 好了,接下来写一个类来创建你想要的女朋友吧
 * CreateGirlfriend
 */


CreateGirlfriend.java



package cn.zlf.code.decoratormode;
/**
 * 创建你想要的女朋友
 */
public class CreateGirlfriend {
    public static void main(String[] args) {
        //想要一个国产妹子
        Girl g1=new CnGirl();
        System.out.println(g1.getDescription());


        //现在想要一个国产黑色长发妹子
        Girl g2=new BlackLongHairGirl(g1);
        System.out.println(g2.getDescription());


        //现在想要一个国产黑色长发大长腿的妹子
        Girl g3=new BigLongLegsGirl(g2);
        System.out.println(g3.getDescription());


        System.out.println("----------------");
        //那如果我想创建一个国外黑色长发大长腿的女朋友呢,一步到位
        Girl g4=new BigLongLegsGirl(new BlackLongHairGirl(new EnGirl()));
        System.out.println(g4.getDescription());
    }
}

结果:



我是国产妹子,
我是国产妹子,黑色长发,
我是国产妹子,黑色长发,大长腿,
----------------
我是外国女孩,黑色长发,大长腿,

上面这个例子就简单的用到了装饰者模式啦,是不是感觉很熟悉其实,在我们平时工作写代码的时候其实这种模式经常用到,只是自己不自知,或者用的不是太规范罢了。所以我们以后工作写代码的时候不妨也按这种思路设计,多想想以后的拓展性和可变性。不然好不容做出一个产品,结果因为每个客户有些特定的需求又得做一个相似的产品就很费时费力了。所以当你需要动态地给一个对象添加功能,实现功能扩展的时候,就可以使用装饰者模式。在Java IO 类中有一个经典的装饰者模式应用, BufferedReader 装饰了 InputStreamReader.

不过装饰者模式缺点也很明显,看上面的例子就可以看出来,才一个这么简单的例子就有好几个类,每一个特性都单独的一个类,这样必然会造成很多相似的代码,且让整个项目显得很臃肿,所以到底使用那种模式还是仁者见仁智者见智了,毕竟没有什么是绝对的,最合适的就是最好的。

最后总结一下适配器模式和装饰者模式的区别:

关于新职责:适配器也可以在转换时增加新的职责,但其主要目的并不在此;而装饰者模式主要目的,就是给被装饰者增加新职责用的。

关于原接口:适配器模式是用新接口来调用原接口,原接口对新系统来说是不可见或者说不可用的;而装饰者模式原封不动的使用原接口,系统对装饰的对象也通过原接口来完成使用。

关于其包裹的对象:适配器是知道被适配者的详细情况的(就是那个类或那个接口);而装饰者只知道其接口是什么,至于其具体类型(是基类还是其他派生类)只有在运行期间才知道。