结构型模式之适配器模式、桥接模式与装饰器模式(一)
一、基本介绍
结构型模式(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等