设计模式05 结构型模式1-适配器+桥接+装饰者

288 阅读16分钟

完整设计模式学习笔记请戳

6. 适配器模式

现实生活中的适配器例子

泰国插座用的是两孔的(欧标),可以买个多功能转换插头 (适配器) ,这样就可以使用了。

1

基本介绍

  1. 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
  2. 适配器模式属于结构型模式
  3. 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式

工作原理

  1. 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
  2. 从用户的角度看不到被适配者,是解耦的
  3. 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
  4. 用户收到反馈结果,感觉只是和目标接口交互,如图
2

类适配器

基本介绍:Adapter 类,通过继承 src 类,实现 dst 类接口,完成 src->dst 的适配。

应用实例

  1. 应用实例说明 以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src (即被适配者),我们的目 dst(即 目标)是 5V 直流电
  2. 思路分析(类图)
3

代码实现 接口

public interface IVoltage5V {
    int output5V();
}

被适配的类

public class Voltage220V {
    public int output220V(){
        int src = 220;
        System.out.println("电压=="+src+"伏");
        return src;
    }
}

适配器

public class VoltageAdapter extends Voltage220V implements IVoltage5V{
    @Override
    public int output5V() {
        //获取到220v电压
        int srcV = output220V();
        int dstV = srcV/44;//转成5v
        return dstV;
    }
}

模拟手机充电

public class Phone {
    //充电方法
    public void charging(IVoltage5V iVoltage5V){
        if (iVoltage5V.output5V()==5){
            System.out.println("电压5v,可以充电");
        }else if (iVoltage5V.output5V()>5){
            System.out.println("电压过高,无法充电");
        }
    }
}

客户端测试

public class Client {
    public static void main(String[] args) {
        Phone phone = new Phone();
        phone.charging(new VoltageAdapter());
    }
}

类适配器模式注意事项和细节

  1. Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点, 因为这要求 dst 必须是接口,有一定局限性;
  2. src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
  3. 由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了。

对象适配器

基本介绍

  1. 基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。 即:持有 src 类,实现 dst 类接口,完成 src->dst 的适配
  2. 根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。
  3. 对象适配器模式是适配器模式常用的一种 4

代码实现
与类适配器相比,只需修改适配器和客户端即可
对象适配器

public class VoltageAdapter  implements IVoltage5V{

    private Voltage220V voltage220V;//关联关系中的聚合关系

    //通过构造器传入
    public VoltageAdapter(Voltage220V voltage220V) {
        this.voltage220V = voltage220V;
    }

    @Override
    public int output5V() {
        int dst = 0;
        if (voltage220V!=null){
            int srcV = voltage220V.output220V();
            System.out.println("使用对象适配器,进行适配");
            dst = srcV/44;
            System.out.println("适配完成,输出电压为"+dst+"伏特");
        }
        return dst;
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        //对象适配器
        Phone phone = new Phone();
        phone.charging(new VoltageAdapter(new Voltage220V()));
    }
}

对象适配器模式注意事项和细节

  1. 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst必须是接口。
  2. 使用成本更低,更灵活。

接口适配器

  1. 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
  2. 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
  3. 适用于一个接口不想使用其所有的方法的情况。

应用案例

  1. Android 中的属性动画 ValueAnimator 类可以通过 addListener(AnimatorListener listener)方法添加监听器, 那么常规写法如右:
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);
        valueAnimator.addListener(new Animator.AnimatorListener() {

            @Override public void onAnimationStart(Animator animation, boolean isReverse) {
                
            }

            @Override public void onAnimationEnd(Animator animation, boolean isReverse) {

            }

            @Override public void onAnimationStart(Animator animation) {
                
            }

            @Override public void onAnimationEnd(Animator animation) {

            }

            @Override public void onAnimationCancel(Animator animation) {

            }

            @Override public void onAnimationRepeat(Animator animation) {

            }
        });
        valueAnimator.start();
  1. 有时候我们不想实现 Animator.AnimatorListener 接口的全部方法,我们只想监听 onAnimationStart,我们会如下写
    ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);
    valueAnimator.addListener(new AnimatorListenerAdapter() {
        @Override public void onAnimationStart(Animator animation) {
            super.onAnimationStart(animation);
            //或其他具体实现
        }
    });
    valueAnimator.start();
  1. AnimatorListenerAdapter 类,就是一个接口适配器,代码如右图:它空实现了 Animator.AnimatorListener 类(src)的所有方法.
public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener,
        Animator.AnimatorPauseListener {

    @Override
    public void onAnimationCancel(Animator animation) {}

    @Override
    public void onAnimationEnd(Animator animation) {}

    @Override
    public void onAnimationRepeat(Animator animation) {}
    
    @Override
    public void onAnimationStart(Animator animation) {}


    @Override
    public void onAnimationPause(Animator animation) {}

    @Override
    public void onAnimationResume(Animator animation) {}
}
  1. AnimatorListener 是一个接口
    public static interface AnimatorListener {

        default void onAnimationStart(Animator animation, boolean isReverse) {
            onAnimationStart(animation);
        }

        default void onAnimationEnd(Animator animation, boolean isReverse) {
            onAnimationEnd(animation);
        }

        void onAnimationStart(Animator animation);

        void onAnimationEnd(Animator animation);

        void onAnimationCancel(Animator animation);

        void onAnimationRepeat(Animator animation);
    }
  1. 程序里的匿名内部类就是 Listener 具体实现类
new AnimatorListenerAdapter() {
        @Override public void onAnimationStart(Animator animation) {
            super.onAnimationStart(animation);
            //或其他具体实现
        }
    }

代码实现
5

接口

public interface Interface4 {
    void m1();
    void m2();
    void m3();
    void m4();
}

抽象类

public abstract class AbsAdapter implements Interface4 {
    //将接口方法进行默认空实现
    @Override
    public void m1() { }

    @Override
    public void m2() { }

    @Override
    public void m3() { }

    @Override
    public void m4() { }
}

客户端使用

public class Client {
    public static void main(String[] args) {
        AbsAdapter abs = new AbsAdapter() {
            //只需要覆盖需要使用的接口方法
            @Override
            public void m1() {
                System.out.println("使用了m1的方法");
            }
        };
    }
}

充电器例子实现
只需修改适配器和客户端调用方式即可
接口

public interface IVoltage {
    int output5V();
    int output10V();
    int output20V();
}

抽象类适配器

public abstract class PowerAdapter implements IVoltage {

    protected Voltage220V voltage220V;

    public PowerAdapter(Voltage220V v){
        this.voltage220V = v;
    }

    @Override
    public int output5V() {
        return 0;
    }

    @Override
    public int output10V() {
        return 0;
    }

    @Override
    public int output20V() {
        return 0;
    }
}

客户端调用

public class Client {
    public static void main(String[] args) {
        Phone phone = new Phone();
        phone.charging(new PowerAdapter(new Voltage220V()) {
            @Override
            public int output5V() {
               return voltage220V.output220V()/44;//可以充电
//                return 6;//电压过高
            }

            @Override
            public int output10V() {
                return voltage220V.output220V()/22;
            }
        });
    }
}

适配器模式在 SpringMVC 框架应用的源码剖析

  1. SpringMVC 中的 HandlerAdapter, 就使用了适配器模式
  2. SpringMVC 处理请求的流程回顾
  3. 使用 HandlerAdapter 的原因分析: 可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用 Controller 方法,需要调用的时候就得不断是使用 if else 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller, 就得修改原来的代码,这样违背了 OCP 原则。

说明

  • Spring定义了一个适配接口,使得每一种Controller有一 种对应的适配器实现类
  • 适配器代替controller执行相应的方法
  • 扩展Controller时,只需要增加一一个适配器类就完成了SpringMVC的扩展了
  • 这就是设计模式的力量

适配器模式的注意事项和细节

  1. 三种命名方式,是根据 src 是以怎样的形式给到 Adapter(在 Adapter 里的形式)来命名的。
  2. 类适配器:以类给到,在 Adapter 里,就是将 src 当做类,继承 对象适配器:以对象给到,在 Adapter 里,将 src 作为一个对象,持有接口适配器:以接口给到,在 Adapter 里,将 src 作为一个接口,实现
  3. Adapter 模式最大的作用还是将原本不兼容的接口融合在一起工作。
  4. 实际开发中,实现起来不拘泥于这三种经典形式

7. 桥接模式

手机操作问题
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图: 6

传统方法对应的类图
7

传统方案解决手机操作问题分析

  1. 扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
  2. 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本.
  3. 解决方案-使用桥接模式

基本介绍

  1. 桥接模式(Bridge 模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
  2. 是一种结构型设计模式
  3. Bridge 模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展

原理类图

8
  1. Client 类:桥接模式的调用者
  2. 抽象类(Abstraction) :维护了 Implementor / 即它的实现类 ConcreteImplementorA.., 二者是聚合关系, Abstraction 充当桥接类
  3. RefinedAbstraction : 是 Abstraction 抽象类的子类
  4. Implementor : 行为实现类的接口
  5. ConcreteImplementorA/B :行为的具体实现类
  6. 从 UML 图:这里的抽象类和接口是聚合的关系,其实调用和被调用关系

代码实现

9

行为实现类的接口(手机品牌接口)

public interface Brand {
    void open();
    void close();
    void call();
}

行为的具体实现类(具体的手机品牌)

public class XiaoMi implements Brand {
    @Override
    public void open() {
        System.out.println("小米手机开机了");
    }

    @Override
    public void close() {
        System.out.println("小米手机关机了");
    }

    @Override
    public void call() {
        System.out.println("小米手机打电话了");
    }
}
public class Vivo implements Brand {
    @Override
    public void open() {
        System.out.println("vivo手机开机了");
    }

    @Override
    public void close() {
        System.out.println("vivo手机关机了");
    }

    @Override
    public void call() {
        System.out.println("vivo手机打电话了");
    }
}

抽象类 (手机样式的抽象类)

public abstract class Phone {
    private Brand brand ;
    public Phone(Brand iBrand){
        this.brand = iBrand;
    }

    protected void open(){
        brand.open();
    }
    protected void close(){
        brand.close();
    }
    protected void call(){
        brand.call();
    }

}

抽象类的子类(具体的手机样式)

public class FoldedPhone extends Phone {

    public FoldedPhone(Brand iBrand) {
        super(iBrand);
    }

    @Override
    protected void open() {
        System.out.println("折叠手机");
        super.open();
    }

    @Override
    protected void close() {
        System.out.println("折叠手机");
        super.close();
    }

    @Override
    protected void call() {
        System.out.println("折叠手机");
        super.call();
    }
}
public class UpRightPhone extends Phone {
    public UpRightPhone(Brand iBrand) {
        super(iBrand);
    }

    @Override
    protected void open() {
        System.out.println("直板手机");
        super.open();
    }

    @Override
    protected void close() {
        System.out.println("直板手机");
        super.close();
    }

    @Override
    protected void call() {
        System.out.println("直板手机");
        super.call();
    }

}

客户端调用

public class Client {
    public static void main(String[] args) {
        Brand vivoBrand = new Vivo();
        Brand xiaomiBrand = new XiaoMi();
        Phone fPhone,uPhone ;
        fPhone= new FoldedPhone(vivoBrand);
        //折叠vivo手机打电话
        fPhone.call();
        uPhone= new UpRightPhone(xiaomiBrand);
        //直板小米手机打电话
        uPhone.call();

    }
}

应用案例

AbsListView 与 ListAdapter 之间的桥接模式 10

桥接模式的注意事项和细节

  1. 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
  2. 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
  3. 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
  4. 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
  5. 桥接模式要求正确识别出系统中两个独立变化的维度(抽象、和实现),因此其使用范围有一定的局限性,即需要有这样的应用场景。 桥接模式其它应用场景

对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用.

常见的应用场景

  1. -JDBC 驱动程序
  2. -银行转账系统 转账分类: 网上转账,柜台转账,AMT 转账 转账用户类型:普通用户,银卡用户,金卡用户..
  3. -消息管理 消息类型:即时消息,延时消息 消息分类:手机短信,邮件消息,QQ 消息...

8. 装饰者模式

需求提出:星巴克咖啡订单项目(咖啡馆)

  1. 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
  2. 调料:Milk、Soy(豆浆)、Chocolate
  3. 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
  4. 使用 OO 的来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖啡+调料组合。

方案一 11

  1. Drink 是一个抽象类,表示饮料
  2. des 就是对咖啡的描述, 比如咖啡的名字
  3. cost() 方法就是计算费用,Drink 类中做成一个抽象方法.
  4. Decaf 就是单品咖啡, 继承 Drink, 并实现 cost
  5. Espress && Milk 就是单品咖啡+调料, 这个组合很多
  6. 问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸

方案二

  1. 前面分析到方案 1 因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这样就不会造成类数量过多。从而提高项目的维护性(如图) 12

  2. 说明: milk,soy,chocolate 可以设计为Boolean,表示是否要添加相应的调料.

问题

  1. 方案 2 可以控制类的数量,不至于造成很多的类
  2. 在增加或者删除调料种类时,代码的维护量很大
  3. 考虑到用户可以添加多份 调料时,可以将 hasMilk 返回一个对应 int
  4. 考虑使用 装饰者 模式

装饰者模式定义

  1. 装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
  2. 这里提到的动态的将新功能附加到对象和 ocp 原则,在后面的应用实例上会以代码的形式体现

原理 13

  1. 装饰者模式就像打包一个快递 主体:比如:陶瓷、衣服 (Component) // 被装饰者 包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator)
  2. Component 主体:比如类似前面的 Drink
  3. ConcreteComponent 和 Decorator ConcreteComponent:具体的主体, 比如前面的各个单品咖啡
  4. Decorator: 装饰者,比如各调料. 在如图的 Component 与 ConcreteComponent 之间,如果 ConcreteComponent 类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象层一个类。

用装饰者模式解决星巴克咖啡订单问题 14

说明

  • Drink 类就是前面说的抽象类,Component
  • ShortBlack就单品咖啡
  • Decorator 是一个装饰类,含有一个被装饰的对象(Drink obj)
  • Decorator的cost方法进行一个费用的叠加计算,递归的计算价格

例:
订单:2 份巧克力+一份牛奶的 LongBlack 15

说明

  • Milk包含了LongBlack
  • 一份Chocolate包含 了(Milk+L ongBlack)
  • 一份Chocolate包 含了(Chocolate+ Milk+LongBlack)
  • 这样不管是什么形式的单品咖啡+调料组合,通过递归方式可以方便的组合和维护。

代码实现

Drink接口

public abstract class Drink {
    public String des;//描述
    private float price = 0.0f;

    //计算费用的抽象方法
    //子类来实现
    public abstract float cost();

    public String getDes() {
        return des;
    }

    public void setDes(String des) {
        this.des = des;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }
}

咖啡继承于Drink

public class Coffee extends Drink {
    @Override
    public float cost() {
        return super.getPrice();
    }
}

三种口味的咖啡

public class LongBlack extends Coffee {

    public LongBlack() {
        setDes("美式咖啡");
        setPrice(15.0f);
    }
}
public class ShortBlack extends Coffee {

    public ShortBlack() {
        setDes("ShortBlack咖啡");
        setPrice(10.0f);
    }
}
public class Espresso extends Coffee {

    public Espresso() {
        setDes("意大利咖啡");
        setPrice(20.0f);
    }
}

装饰者

public class Decorator extends Drink {

    private Drink obj;

    public Decorator(Drink obj) {
        this.obj = obj;
    }

    @Override
    public float cost() {
        //自己的价格
        return super.getPrice()+obj.cost();
    }

    @Override
    public String getDes() {
        //信息+价格+被装饰者的信息
        return super.des+""+super.getPrice()+" && "+obj.getDes();
    }
}

具体的装饰者,调味品

public class Milk extends Decorator {
    public Milk(Drink obj) {
        super(obj);
        setDes("牛奶调味品");
        setPrice(2.0f);//调味品的价格
    }
}
public class Soy extends Decorator {
    public Soy(Drink obj) {
        super(obj);
        setDes("豆浆调味品");
        setPrice(1.0f);//调味品的价格
    }
}
public class Chocoolate extends Decorator {
    public Chocoolate(Drink obj) {
        super(obj);
        setDes("巧克力调味品");
        setPrice(3.0f);//调味品的价格
    }
}

客户端调用

public class CoffeeBar {
    public static void main(String[] args) {
        //装饰者模式下的订单  2份巧克力+1份牛奶的LongBlack

        //1. 点一份LongBlack
        Drink order = new LongBlack();
        System.out.println("单品价格"+order.cost());
        System.out.println("单品描述"+order.getDes());

        //2. 加入一份牛奶
        order = new Milk(order);
        System.out.println("加入牛奶的费用=="+order.cost());
        System.out.println("加入一份牛奶的描述=="+order.getDes());

        //3. 加入一份巧克力
        order = new Chocoolate(order);
        System.out.println("加入巧克力的费用=="+order.cost());
        System.out.println("加入一份巧克力的描述=="+order.getDes());

        //4. 再加入一份巧克力
        order = new Chocoolate(order);
        System.out.println("再加入巧克力的费用=="+order.cost());
        System.out.println("再加入一份巧克力的描述=="+order.getDes());

    }
}

控制台输出

单品价格15.0
单品描述LongBlack咖啡
加入牛奶的费用==17.0
加入一份牛奶的描述==牛奶调味品2.0 && LongBlack咖啡
加入巧克力的费用==20.0
加入一份巧克力的描述==巧克力调味品3.0 && 牛奶调味品2.0 && LongBlack咖啡
加入巧克力的费用==23.0
加入一份巧克力的描述==巧克力调味品3.0 && 巧克力调味品3.0 && 牛奶调味品2.0 && LongBlack咖啡

装饰者模式在 JDK 应用的源码分析

Java 的 IO 结构,FilterInputStream 就是一个装饰者 16

public class Decorator {

    public static void main(String[] args) throws Exception{
        //说明
        //1. InputStream  是抽象类,  类似我们前面讲的 Drink
        //2. FileInputStream 是	InputStream  子类,类似我们前面的 DeCaf, LongBlack
        //3. FilterInputStream	是	InputStream 子类:类似我们前面 的 Decorator 修饰者
        //4. DataInputStream  是 FilterInputStream  子类,具体的修饰者,类似前面的 Milk, Soy 等
        //5. FilterInputStream 类  有	protected volatile InputStream in;  即含被装饰者
        //6. 分析得出在 jdk 的 io 体系中,就是使用装饰者模式
        
        DataInputStream dis = new DataInputStream(new FileInputStream("d:\\abc.txt"));
        System.out.println(dis.read());
        dis.close();
    }
}


设计模式学习代码及笔记)

【代码】
github.com/willShuhuan…
【笔记】
设计模式01 七大原则
设计模式02 类关系与UML类图
设计模式03 创建型模式1-单例+工厂
设计模式04 创建型模式2-原型+建造者
设计模式05 结构型模式1-适配器+桥接+装饰者
设计模式06 结构型模式2-组合+外观+享元+代理
设计模式07 行为型模式1-模板方法+命令+访问者
设计模式08 行为型模式2-迭代器+观察者+中介者
设计模式09 行为型模式3-备忘录+解释器+状态
设计模式10 行为型模式4-策略+职责链