结构型模式之适配器模式、桥接模式与装饰器模式(一)

一、基本介绍

  结构型模式(Structural Pattern)关注如何将现有类或对象组织在一起形成更加强大的结构。分为两种:1,类结构型模式:关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系;2,对象结构型模式:关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法,更符合“合成复用原则”。

  具体的结构型模式可分为:

  • 适配器模式(Adapter Pattern)
  • 桥接模式(Bridge)
  • 装饰者模式(Decorator)
  • 组合模式(Composite Pattern)
  • 外观模式(Facade)
  • 享元模式(Flyweight Pattern)
  • 代理模式(Proxy)

二、适配器模式

1,基本介绍

  适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主要的目的是兼容性,让原本接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。

  适配器模式属于结构型模式,主要分为三类:类适配器模式、对象适配器模式、接口适配器模式

2,工作原理

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

3,类适配器模式

a)类适配器模式介绍

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

b)类适配器应用实例

 需求

  以生活中充电器为例,充电器本身相当于Adapter,220V交流电相当于src(即被适配者),我们的目标dst(即目标)是5V直流电

 思路分析(类图)

    

 代码实现

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

public interface IVoltage5V {
    public int output5v();
}

public class VoltageAdapter extends Voltage220V implements IVoltage5V {
    @Override
    public int output5v() {
        int v220 = output220v();
        return v220 / 44;
    }
}

public class Phone {
    public void chrging(IVoltage5V iVoltage5V) {
        if (iVoltage5V.output5v() == 5) {
            System.out.println("电压5V,可以充电");
        } else {
            System.out.println("电压" + iVoltage5V.output5v() + "V,无法充电");
        }
    }
}

public class Client {
    public static void main(String[] args) {
        System.out.println("==========类适配器模式=========");
        Phone phone = new Phone();
        phone.chrging(new VoltageAdapter());
    }
}

c)注意事项与细节

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

4,对象适配器模式

a)对象适配器模式介绍

  基本思路和类的适配器模式相同,只是将Adapter类做修改,不再继承src类,而是持有src类的实例(聚合),以解决兼容性的问题。即:持有src类,实现dst类接口,完成src -> dst的适配。

  即根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。对象适配器模式是适配器模式中常用的一种

b)对象适配器应用实例

 需求

  同类适配器

 思路分析(类图)

  

 代码实现

  除了VoltageAdapter类之外,其他的类与类适配器相同

public class VoltageAdapter implements IVoltage5V {

    private Voltage220V voltage220V;

    public VoltageAdapter(Voltage220V voltage220V) {
        this.voltage220V = voltage220V;
    }

    @Override
    public int output5v() {
        int v220 = voltage220V.output220v();
        return v220 / 44;
    }
}

c)注意事项与细节

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

5,接口适配器模式

a)接口适配器模式介绍

  • 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。
  • 适用于一个接口不想使用其他所有的方法的情况

b)接口适配器模式实例

 类图

  

 代码实现

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

public abstract class AbsAdapter implements InterfaceAdapter {
    @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 absAdapter = new AbsAdapter() {
            @Override
            public void m1() {
                System.out.println("test absAdapter m1()");
            }
        };
        absAdapter.m1();
    }
}

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

  • 三种命名方式,是根据src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。
  • 类适配器:以类给到Adapter里,也就是将src当做类,继承
  • 对象适配器:以对象给到Adapter里,也就是将src作为一个对象,持有
  • 接口适配器:以接口给到Adapter里,也就是将src作为一个接口,实现
  • Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作

三、桥接模式

1,手机操作问题

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

  

 传统方式解决手机操作问题

 类图

  

 分析

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

2,桥接模式

a)基本介绍

  桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。它是一种结构型设计模式。

  Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。

b)原理类图

  

 原理类图说明

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

c)解决手机操作问题

 类图

  

 代码

public interface Brand {
    void open();

    void call();

    void close();

}

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

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

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

public abstract class Phone {
    private Brand brand;

    public Phone(Brand brand) {
        this.brand = brand;
    }

    protected void open() {
        this.brand.open();
    }

    protected void call() {
        this.brand.call();
    }

    protected void close() {
        this.brand.close();
    }

}

public class FoldedPhone extends Phone{

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

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

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

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

public class Client {
    public static void main(String[] args) {
        FoldedPhone foldedPhone = new FoldedPhone(new Xiaomi());
        foldedPhone.open();
    }
}

3,注意事项

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

4,常用的场景

  • JDBC驱动
  • 银行转账系统

    • 转账分类: 网上转账,柜台转账,AMT 转账
    • 转账用户类型:普通用户,银卡用户,金卡用户...
  • 消息管理
    • 消息类型:即时消息,延时消息
    • 消息分类:手机短信,邮件消息,QQ 消息...

四、装饰者模式

1,咖啡订单问题

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

2,解决方案

a)方案一

 类图分析

  1)Drink 是一个抽象类,表示饮料

   -description 就是对咖啡的描述, 比如咖啡的名字

   -cost()方法就是计算费用,Drink 类中做成一个抽象方法.

  2)Decaf 就是单品咖啡,继承 Drink, 并实现了 cost()

  3)Espress && Milk 就是单品咖啡+调料, 这个组合很多

 问题:

  这样设计会有很多类,当我们增加一个单品咖啡,或者一个新的调料时,类的数量就会倍增,就会出现类爆炸

b)方案二

 

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

 方案二问题分析

  1) 方案 2 可以控制类的数量,不至于造成很多的类

  2) 在增加或者删除调料种类时,代码的维护量很大

  3) 考虑到用户可以添加多份调料,可以将 hasMilk 返回一个对应 int

  4) 改进:考虑使用 装饰者 模式

3,装饰者模式

a)基本介绍

  装饰者模式(Decorator):动态地将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(OCP)。

b)原理

  •  装饰者模式就像打包一个快递
    • 主体:陶瓷、衣服(Component) //被装饰者
    • 包装:报纸填充、纸板、木板(Decorator)//装饰者
  • Component:主体,类似前面的Drink
  • ConcreteComponent:具体的主体,比如前面的各种咖啡
  • Decorator:装饰者,比如各种调料。装饰者里聚合了一个Component,也就是ConcreteComponent是可以放到装饰者里面的(装饰者里面可以包含被装饰者)
  • ConcreteDecorator:具体的装饰者,比如前面的各种调料

c)完成咖啡订单问题

 uml类图

  代码

//饮料父类
public abstract class Drink {
    private String description;

    private float price = 0.0f;

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public float getPrice() {
        return price;
    }

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

    //消费由子类具体实现
    public abstract float cost();
}

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

    @Override
    public String getDescription() {
        return super.getDescription() + ":" + cost();
    }
}
//美式咖啡
public class LongBlack extends Coffee{
    public LongBlack() {
        setDescription("美式...黑咖啡");
        setPrice(2.0f);
    }
}
//装饰类,调料
public class Decorator extends Drink{
    Drink drink;

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

    @Override
    public float cost() {
        return drink.cost() + super.getPrice();
    }

    @Override
    public String getDescription() {
        return super.getDescription() + " " + super.getPrice() + " " + drink.getDescription();
    }
}
//具体的装饰调料,牛奶
public class Milk extends Decorator{
    public Milk(Drink drink) {
        super(drink);
        setPrice(1.5f);
        setDescription("牛奶");
    }
}

//测试
public class Client {
    public static void main(String[] args) {
        Drink order = new LongBlack();
        System.out.println("单品:"+order.getDescription()+" 价格:"+order.cost());
        //加一份巧克力
        order = new Chocolate(order);
        System.out.println("加一份巧克力:"+order.getDescription()+" 价格:"+order.cost());
        order = new Milk(order);
        System.out.println("加一份巧克力和一份牛奶:"+order.getDescription()+" 价格:"+order.cost());
    }
}

4,JDK中的应用实例

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

public abstract class InputStream implements Closeable {
  ......  //Component  
}
//Decorator抽象的装饰者 class FilterInputStream extends InputStream { //被装饰的对象 protected volatile InputStream in; ...... }
//具体的装饰者 public class DataInputStream extends FilterInputStream implements DataInput { ...... }
  • InputStream时抽象类,类似前面的Drink(被装饰者)
  • FileInputStream是InputStream子类,类似前面的LongBlack、ShortBlack等单品咖啡
  • FIlterInputStream是InputStream子类,类似前面的Decorator装饰者
  • DataInputStream是FilterInputStream子类,具体的装饰者,类似前面的Milk、Chocolate等 

 

posted @ 2021-10-28 10:00  MXC肖某某  阅读(589)  评论(0编辑  收藏  举报