6.结构型设计模式-适配器模式
6.1.适配器模式简介
(1)简介
- 适配器模式(Adapter Pattern),作为两个不兼容接口的桥梁,属于结构型设计模式,适配器模式可以使原本由于接口不兼容而不能一起工作的类,一起工作。
(2)常见的几种适配器模式
- 类的适配器模式
- 想将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
- 对象的适配器模式
- 想将一个对象转化成满足另一个新接口的对象时,可以创建一个适配器类,持有原类一个实例,在适配器类的方法中,调用实例的方法就行。
- 接口适配器模式
- 不想实现一个接口中的所有方法时,可以创建一个Adapter,实现所有方法,在写别的类的时候,继承Adapter类。
(3)案例
JDBC给出一个客户端通用的抽象接口,每一个具体数据库厂商 如 SQL Server、Oracle、MySQL等,就会开发JDBC驱动,就是一个介于JDBC接口和数据库引擎接口之间的适配器软件
6.2.适配器模式案例
- 总结
- 在使用一些旧系统或者时类库时,经常会出现接口不兼容的问题,适配器模式在解决这类问题上占据优势。
6.3.接口适配器案例实战
(1)接口适配器
有些接口中有很多抽象的方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有事时比较浪费,应为并不是所有的方法都是我们需要的,有事只需要是线部分接口就行了。
(2)编码实现
- 顶级接口PayGateway
public interface PayGateway { void unifiedOrder(); void refund(); void query(); void sendRedPack(); }
- 适配器类PayGatewayAdapter
//可以默认空实现,让继承的子类去实现 public class PayGatewayAdapter implements PayGateway { @Override public void unifiedOrder() { } @Override public void refund() { } @Override public void query() { } @Override public void sendRedPack() { } }
- 实现类ProductVideoOrder
/** * 订单类只需实现支付和退款,无需实现查询等功能 */ public class ProductVideoOrder extends PayGatewayAdapter{ @Override public void unifiedOrder() { System.out.println("下单"); } @Override public void refund() { System.out.println("退款"); } }
- 测试
ProductVideoOrder productVideoOrder = new ProductVideoOrder(); productVideoOrder.refund(); productVideoOrder.unifiedOrder();
6.4.类适配器案例实战
- 要对一个老的类中新增一个方法,但是不想修改老的类,将一个老的类中的一个方法集成到一个新的类中。
- 目标原始类OldModule持有methodA方法。
- 创建目标类接口,持有原始类methodA的抽象,以及新的方法methodB()抽象。
- 创建适配器类,继承原始类,实现目标类接口,实现新的抽象方法methodB()方法。
- 客户端调用适配器类就可以集成调用methodA方法、和methodB方法。
(1)案例实战
- 目标原始类OldModule
public class OldModule { public void methodA(){ System.out.println("Old methodA()"); } }
- 新目标类接口TargetModule
public interface TargetModule { /** * 和需要适配的老的类的方法名相同 */ void methodA(); /** * 新的方法 */ void methodB(); }
- 适配器类Adapter,继承OldModule,实现TargetModule接口
public class Adapter extends OldModule implements TargetModule { @Override public void methodB() { System.out.println("New methodB()"); } }
- 测试
ProductVideoOrder productVideoOrder = new ProductVideoOrder(); productVideoOrder.refund(); productVideoOrder.unifiedOrder();
(2)优点和缺点
- 优点
- 可以让两个没有关联的类一起运行,使原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- 增加灵活度,提高复用性,适配器类可以在多个系统使用,符合开闭原则。
- 缺点
- 整体类的调用链路增加,本来A可以直接调用C,使用适配器后,要先经过适配器类在调用C
7.结构型设计模式-桥接模式
7.1.桥接模式简介
(1)简介
- 与适配器模式类似,根据不同的场景进行搭配,桥接设计模式也是结构型设计模式。
- 将抽象部分与实现部分分离,是他们都可以独立的变化,通过组合来桥接其他的行为、维度。
(2)应用场景
- 系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性。
- 不想使用继承导致系统类的个数急剧增加的系统。
- 有时候一个类,可能拥有多个变化维度,使用桥接模式就可以解决这个问题,且解耦。
(3)继承方式与桥接模式对比
- 创建手机的抽象类Phone,持有Color的实例,定义抽象方法useColor()。
- 创建实现类SXPhone,实现run方法,显式调用父类的构造。
- 创建接口Color,定义抽象方法,userColor()。
- RedColor、YellowColor分别实现Color接口,实现useColor()方法。
- 客户端在使用的时候直接将颜色的实例通过构造传给手机对象即可。
7.2.桥接模式案例实战
(1)编码实战
- 编写颜色接口Color
public interface Color { void useColor(); }
- 编写颜色实现类RedColor、BlueColor、YellowColor
public class BlueColor implements Color { @Override public void useColor() { System.out.println("蓝色"); } } public class RedColor implements Color { @Override public void useColor() { System.out.println("红色"); } } public class YellowColor implements Color { @Override public void useColor() { System.out.println("黄色"); } }
- 编写Phone抽象类
public abstract class Phone { //将color作为一个属性桥接进来 protected Color color; public void setColor(Color color) { this.color = color; } /** * 运行方法 */ abstract public void run(); }
- phone的具体实现类PGPhone、HWPhone、SXPhone
public class SXPhone extends Phone { public SXPhone(Color color) { super.setColor(color); } @Override public void run() { color.useColor(); System.out.println("三星手机"); } } public class HWPhone extends Phone { public HWPhone(Color color){ super.setColor(color); } @Override public void run() { color.useColor(); System.out.println("华为手机"); } } public class PGPhone extends Phone { public PGPhone(Color color){ super.setColor(color); } @Override public void run() { color.useColor(); color.useColor();System.out.println("苹果手机"); } }
- 测试
Phone redHWPhone = new HWPhone(new RedColor()); redHWPhone.run(); Phone blueHWPhone = new HWPhone(new BlueColor()); blueHWPhone.run();
(2)优点和缺点
- 优点
- 抽象和分离实现。
- 扩展能力强,符合开闭原则。
- 缺点
- 增加系统的设计难度。
- 桥接模式用于设计的前期,精细化产品设计,适配器模式适用于实际完成之后,发现一些类不能一起工作。
8.结构型设计模式-组合模式
8.1.组合模式简介
(1)简介
- 组合设计模式又叫部分整体模式,将对象组合成树形结构以表示“部分-整体”的层次结构,可以更好的实现管理操作。
- 组合模式是得用户可以使用一致的方法操作单个对象和组合对象
- 部分-整体对象的基本操作多数是一样的,但是应用还是会有不一样的地方。
- 核心:组合模式可以使用一棵树来表示
(2)应用场景
- 文件夹和文件,都有增加、删除等api,有层级管理关系。
- 公司后台部门的阶级组成等等,只要是想要表达对象的部分整体的层次机构都可以使用。
- (3)角色
- 组合部件(Component):他是一个抽象接口,表示树根,(C://)
- 合成部件(Composite):和组合部件类似,也有自己的子节点,(文件夹)
- 叶子(Leaf):在组合中表示子节点对象,注意是没有子节点,(文件)
8.2.组合模式案例实战
(1)编码实现
- 创建节点抽象类Root
public abstract class Root { private String name; public Root(String name){ this.name=name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public abstract void addFile(Root root); public abstract void removeFile(Root root); public abstract void display(int depth); }
- 创建文件夹实体类Folder
public class Folder extends Root { List<Root> folders = new ArrayList<>(); public Folder(String name) { super(name); } @Override public void addFile(Root root) { folders.add(root); } @Override public void removeFile(Root root) { folders.remove(root); } @Override public void display(int depth) { StringBuffer stringBuffer = new StringBuffer(); for (int i = 0; i < depth; i++) { stringBuffer.append("-"); } System.out.println(stringBuffer.toString()+this.getName()); for (Root folder : folders) { //每个下级两个横线 folder.display(depth+2); } } }
- 创建文件实体类File
public class File extends Root { public File(String name) { super(name); } @Override public void addFile(Root root) { } @Override public void removeFile(Root root) { } @Override public void display(int depth) { StringBuffer stringBuffer = new StringBuffer(); for (int i = 0; i < depth; i++) { stringBuffer.append("-"); } System.out.println(stringBuffer.toString()+this.getName()); } }
- 测试
Root root = new Folder("C://"); Root folder1 = new Folder("桌面"); Root folder2 = new Folder("公共"); Root folder3 = new Folder("图像"); Root folder4 = new Folder("lixiang"); Root file1 = new File("lixiang.txt"); root.addFile(folder1); root.addFile(folder2); root.addFile(folder3); folder1.addFile(folder4); folder4.addFile(file1); root.display(0);
(2)优点和缺点
- 优点
- 客户端只需要面对一直的对象而不用考虑整体部分或者节点叶子的问题。
- 方便创建出复杂的层次结构。
- 缺点
- 客户端需要花费更多的时间清理之间的层次关系。
9.结构型设计模式-装饰器模式
9.1.装饰器模式简介
(1)简介
- 装饰器设计模式(Decorator Pattern)
- 属于结构型模式,它是作为现有的类的包装,允许向一个现有的对象添加新的功能,同时又不改变其结构
- 给对象增加功能,一般两种方式 继承或关联组合,将一个的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为来增强功能,这个就是装饰器模式,比继承模式更加灵活
- jdk源码中用的最多装饰器模式的就是IO流
(2)装饰器模式的类图
- 创建抽象接口Component,被装饰类和装饰类共同实现Component。
- 创建装饰类的具体装饰类,具体装饰类中持有被装饰者的实例。
- 客户端创建具体装饰类,传入装饰者实例。
- (3)角色(被装饰的类和装饰类都拥有相同的超类)
- **抽象组件(Component):**定义装饰方法的规范
- **被装饰者(ConcreteComponent):**Component的具体实现也就是我们要装饰的类
- **装饰者组件(Decorator):**定义具体装饰者的行为规范,和Component角色有相同的接口,持有组件Component对象的实例引用
- **具体实现物(ConcreteDecorator):**负责给构建对象装饰附加的功能
9.2.装饰器模式案例实战
(1)编码实战
- 顶层接口Car
public interface Car { String getDec(); int getPrice(); }
- 被装饰实现类BMCar、ADCar
public class ADCar implements Car { private String dec = "奥迪汽车"; @Override public String getDec() { return dec; } @Override public int getPrice() { return 200; } } public class BMCar implements Car { private String dec = "宝马汽车"; @Override public String getDec() { return dec; } @Override public int getPrice() { return 100; } }
- 装饰器类CarDecorator
public class CarDecorator implements Car { private String dec = "汽车装饰器"; @Override public String getDec() { return dec; } @Override public int getPrice() { return 0; } }
- 具体实现装饰类TailCarDecorator
public class TailCarDecorator extends CarDecorator { private Car car; public TailCarDecorator(Car car){ this.car = car; } private String dec = "增加一个尾翼"; @Override public String getDec() { return car.getDec()+dec; } @Override public int getPrice() { return car.getPrice()+10; } }
- 测试
Car car = new TailCarDecorator(new BMCar()); System.out.println(car.getDec());
(2)优点和缺点
- 优点
- 装饰模式与继承关系的目的都是要扩展对象的功能,但装饰模式可以提供比继承更多的灵活性。
- 使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,原有代码无需改变,符合开闭原则。
- 缺点
- 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。
- 增加系统的复杂度。
9.3.JDK源码中的装饰器模式
- InputStream IO流
- 抽象组件(Component):InputStream定义装饰方法的规范。
- 被装饰者(ConcreteComponent):FileInputStream、ByteArrayInputStream,被装饰的实现类。
- 装饰者组件(Decorator):FilterInputStream定义具体装饰者的行为规范,和Component角色有相同的接口,
- 持有Component的实现的对象实例。
- 具体装饰物:BufferedInputStream、DataInputStream负责给Component的实现对象增加附加功能。
- 类图
10.结构型设计模式-代理模式
10.1.代理模式简介
(1)代理设计模式(Proxy Pattern)
- 为其他对象提供一种代理以控制对这个对象的访问,属于结构型模式。
- 客户端并不直接调用真实对象,而是通过掉用代理,来间接调用真实的对象。
- (2)角色
- Subject:抽象接口,真实对象和代理对象都要实现的一个抽象接口。
- Proxy:包含了对真实对象的引用,从而可以随意的操作真实对象的方法。
- RealProject:真实对象。
(3)类关系图
- 创建公共接口,代理类对象和真实类对象都实现接口。
- 客户端直接调用代理类对象。
10.2.代理模式案例实战
(1)编码实现
- 目标接口DigitalSell
public interface DigitalSell { void sell(); }
- 真实类DigitalSellReal
public class DigitalSellReal implements DigitalSell { @Override public void sell() { System.out.println("方法执行"); } }
- 代理类DigitalSellProxy
public class DigitalSellProxy implements DigitalSell { private DigitalSellReal digitalSellReal = new DigitalSellReal(); @Override public void sell() { System.out.println("前置增强"); digitalSellReal.sell(); System.out.println("后置增强"); } }
- 测试类
DigitalSell digitalSellReal = new DigitalSellReal(); System.out.println("真实对象执行:"); digitalSellReal.sell(); System.out.println("------------------------------"); DigitalSell digitalSellProxy = new DigitalSellProxy(); System.out.println("代理对象执行:"); digitalSellProxy.sell();
(2)JDK动态代理
- 准备目标接口HelloService
/** * 真实抽象类 */ public interface HelloService { String hello(String name); }
- 目标接口的实现HelloServiceImpl
/** * 真实实现类 */ public class HelloServiceImpl implements HelloService { @Override public String hello(String name) { return "Hello:"+name; } }
- 编写代理类JDKProxy,实现InvocationHandler接口,重写invoke方法
public class JDKProxy implements InvocationHandler { //定义真实业务类对象 private Object object; //通过构造方法设置真实业务类对象 public JDKProxy(Object object) { this.object = object; } /** * @param proxy 代理对象 * @param method 代理方法对象 * @param args 参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("PROXY:"+proxy.getClass().getName()); Object result = method.invoke(object, args); System.out.println(method.getClass().getName()); return result; } }
- 测试
HelloService service = new HelloServiceImpl(); //生成代理类的class对象 Class<?> proxyClass = Proxy.getProxyClass(service.getClass().getClassLoader(), service.getClass().getInterfaces()); //创建自定义的InvocationHandler,传入真实类对象 InvocationHandler invocationHandler = new JDKProxy(service); //获取代理类的构造器对象 Constructor constructor = proxyClass.getConstructor(new Class[] {InvocationHandler.class}); //反射创建代理对象 HelloService proxyService = (HelloService)constructor.newInstance(invocationHandler); System.out.println(proxyService.hello("李祥"));
(3)GGLIB动态代理
- 不用接口,直接代理实现类HelloServiceImpl
/** * 真实实现类 */ public class HelloServiceImpl implements HelloService { @Override public String hello(String name) { return "Hello:"+name; } }
- 编写动态代理类,实现MethodInterceptor接口,重写intercept
public class CGLIBProxy implements MethodInterceptor { //创建真实类对象的引用 private Object object; //设置真是类对象 public CGLIBProxy(Object object) { this.object = object; } //为目标类创建代理对象 public Object getProxyInstance(){ //工具类 Enhancer en = new Enhancer(); //设置父类 en.setSuperclass(object.getClass()); //设置回调函数 en.setCallback(this); //创建子类的代理对象 return en.create(); } /** * * @param o 代理对象 * @param method 代理对象方法 * @param objects 参数 * @param methodProxy 方法的代理 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { Object aSuper = methodProxy.invokeSuper(o, objects); return aSuper; } }
- 测试
//创建目标对象 HelloServiceImpl helloService = new HelloServiceImpl(); HelloServiceImpl helloServiceProxy =(HelloServiceImpl) new CGLIBProxy(helloService).getProxyInstance(); System.out.println(helloServiceProxy); System.out.println(helloServiceProxy.hello("李祥"));
(4)JDK动态代理和CGLIB动态代理的区别
- JDK动态代理:要求目标对象实现一个,但是又的时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以用CGLIB动态代理。
- CGLIB动态代理,他是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
- JDK动态代理是自带的,CGLIB需要引入第三方包。
- CGLIB动态代理基于继承来实现代理,所以无法对final类、private方法和static方法实现代理。
(5)优点和缺点
- 优点
- 可以在访问一个类时做一些控制,或增加功能。
- 操作代理类无需修改原本的源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
- 缺点
- 增加系统的复杂性和调用的链路
11.结构型设计模式-外观模式
11.1.外观模式简介
(1)简介
外观设计模式(Facade Pattern):门面模式,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问的系统接口,定义一个高层的接口使得这个接口更加容易使用。
(2)应用场景
- 开发里面MVC三层架构,在数据访问层、业务逻辑层和表示层之间用interface接口进行相互交互,客户端访问只需访问接口,无需知道其内部实现方式。
- 对于复杂难以维护的老系统进行扩展,可以用外观设计模式。
- 需要对一个复杂的模块或子系统提供一个外界访问的接口,外界对子系统的访问只要黑盒操作。
- (3)角色
- 外观角色(Facade):客户端可以调用这个角色的方法,这个外观方法知道多个子系统的功能和实际调用。
- 子系统角色(SubSystem):每个子系统都可以被客户端直接调用,子系统并不对外暴漏。
(4)类图关系
- 创建子系统A、子系统B、子系统C。
- 创建外观类Facade,分别持有A、B、C的实例。
11.2.外观模式案例实战
(1)代码实现
- 顶层接口IMessageManager
public interface IMessageManager { void send(); }
- 子系统实现SmsMessageManager、MailMessageManager、DingDingMessageManager
public class DingDingMessageManager implements IMessageManager{ @Override public void send() { System.out.println("发送钉钉信息"); } } public class MailMessageManager implements IMessageManager{ @Override public void send() { System.out.println("发送邮件信息"); } } public class SmsMessageManager implements IMessageManager{ @Override public void send() { System.out.println("发送短信信息"); } }
- 外观类MessageFacade
public class MessageFacade implements IMessageManager{ private IMessageManager dingDingMessageManager = new DingDingMessageManager(); private IMessageManager smsMessageManager = new SmsMessageManager(); private IMessageManager mailMessageManager = new MailMessageManager(); @Override public void send() { dingDingMessageManager.send(); smsMessageManager.send(); mailMessageManager.send(); } }
- 测试
IMessageManager messageFacade = new MessageFacade(); messageFacade.send();
(2)优点和缺点
- 优点
- 减少了系统的相互依赖,提高了灵活性。
- 符合依赖倒转原则:针对接口编程,依赖于抽象而不依赖于具体。
- 符合迪米特法则:最少知道原则,一个实体应当尽量少的与其他实体间发生相互作用。
- 缺点
- 增加了系统的类和链路
- 不是很符合开闭原则,如果增加了新的骆及,需要修改facade外观类