这 4 个模式很相近
适配器模式:包装另一个对象,并提供不同的接口。
外观模式:包装许多对象,以简化他们的接口。
装饰者模式:包装另一个对象,并提供额外的行为。(额外的行为可以是灵活的多个装饰器(class)完成的, 也就是可以 n:1)
代理模式:包装另一个对象,并控制对它的访问. (只能 1:1 的代理一个对象)
区别
从它们的定义中可以总结出以下几点区别:
1)适配器模式强调的是转换接口。举例:JDBC是java定义的数据库操作规范,其已经定义好了操作数据库的接口,假设一个数据库厂商提供了一套操作数据库的接口,但是该接口并不符合JDBC的规范,也就是说JDBC规范规定的接口和数据库厂商提供的接口不一致。这个时候就可以使用适配器模式将数据库厂商的接口转换成JDBC规范要求的接口。其他三种模式均不提供接口转换的功能。
2)外观模式强调的是包装多个对象,以简化他们的接口。想象这样一种场景:你家的电器都是智能控制的,当你回家的时候你要打开空调、打开电视、打开热水器等等,这些都需要你自己一个个的去操作,这个时候你就会有这样一种需求,就是当你到家的时候只要进行一个操作,就能依次打开空调、电视、热水器等。这个时候就可以使用外观模式将空调、电视、热水器等的接口进行包装,只对外提供一个按钮。其他三种模式均强调的是对一个对象的包装。
3)装饰者模式强调的是为被装饰对象增加额外的行为。举例:java.io包。
4)代理模式强调的是对被代理对象进行控制。这些控制体现在很多方面,比如安全、权限控制等。
适配器模式和外观模式容易和其他模式进行区分。下面重点比较装饰者模式和代理模式。
5)装饰者模式和外观模式主要的区别就是,装饰者模式从来不创建被装饰的对象,它总是添加新功能到已经存在的对象上面;而代理模式在被代理对象不存在的时候会创建被代理对象。
6)装饰者模式可以通过嵌套装饰添加多重额外功能,而代理模式一般不推荐使用嵌套代理。
适配器模式
JAVA.IO 的各种流之间的转换, 实际上是使用的适配器.
package com.leon.design; import java.lang.annotation.Target; public class ClientAdapter { public static void main(String[] args) { ClientAdapter clientAdapter = new ClientAdapter(); Adpatee adpatee = new Adpatee(); AdapterTarget target = new MyAdapter(adpatee); // 客户本来的需求充电 clientAdapter.charge(target); } public void charge(AdapterTarget t) { t.handleRequest(); } } package com.leon.design; public interface AdapterTarget { void handleRequest(); } package com.leon.design; /** * 适配器 */ public class MyAdapter implements AdapterTarget { private Adpatee adpatee; @Override public void handleRequest() { adpatee.request(); } public MyAdapter(Adpatee adpatee) { this.adpatee = adpatee; } } package com.leon.design; /** * 被适配的类 * */ public class Adpatee { // 欧洲的具体充电方法 public void request() { System.out.println("欧洲充电功能"); } }
代理模式
核心功能唱歌, 并没有改变, 代理帮助歌星做一些其他的地方, 面向切面编程AOP, 与装饰者模式的区别是, 装饰者是针对唱歌本身, 增加一些装饰, 为装饰对象增加一些额外的功能. 控制唱歌的方法, 要想让明星唱歌, 必须要先找代理谈价格.
静态代理: 我们自己定义代理类
动态代理: 有程序自动生成代理类
静态代理:
package com.leon.design; public class ClientStaticProxy { public static void main(String[] args) { StarPlay singer = new SingerStar("周杰伦"); StarPlay dancer = new DancerStar("王天一"); ProxyStar proxySinger = new ProxyStar(singer); ProxyStar proxyDance = new ProxyStar(dancer); proxySinger.play(); proxyDance.play(); } } package com.leon.design; public interface StarPlay { void play(); } package com.leon.design; public class ProxyStar implements StarPlay{ StarPlay target; // 代理人需要帮助star 收取费用和收尾工作 @Override public void play() { System.out.println("Proxy: Play 之前, 先收钱"); target.play(); System.out.println("Proxy: Play 之后, 再收尾"); } public ProxyStar(StarPlay target) { this.target = target; } } package com.leon.design; public class SingerStar implements StarPlay{ private String singerName; @Override public void play() { System.out.println("I am " + singerName + ", i can sing."); } public SingerStar(String singerName) { this.singerName = singerName; } } package com.leon.design; public class DancerStar implements StarPlay{ private String dancerName; @Override public void play() { System.out.println("I am "+ dancerName +", I can dance."); } public DancerStar(String dancerName) { this.dancerName = dancerName; } }
动态代码:
代理对象不需要实现接口, 但是目标对象(RealSubject) 仍然需要实现接口.
代理对象动态生成,利用 JDK API 在内存中动态构建代理对象
package com.leon.design; public class ClientDynimicProxy { public static void main(String[] args) { // 目标对象, 也是被代理对象 StarPlay target = new SingerStar("宋祖英"); // 给目标对象创建代理, 可以强转为接口 StarPlay proxyInstance = (StarPlay) new ProxyFactory(target).getProxyInstance(); proxyInstance.play(); // 输出是 proxyInstance: com.leon.design.SingerStar@6bc7c054 // System.out.println("proxyInstance: " + proxyInstance); // proxyInstance's type: class com.sun.proxy.$Proxy0 // System.out.println("proxyInstance's type: " + proxyInstance.getClass()); } } package com.leon.design; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;; // 这里的代码完全是可以直接利用的, 目标对象是 Object 类型的 public class ProxyFactory { private Object target; public ProxyFactory(Object target) { this.target = target; } // 给目标对象动态生成一个代理对象 // 参数1 ClassLoader loader: 制定当前目标对象使用的类加载器 // 参数2 Class<?>[] interface: 目标对象实现的接口类型, 使用泛型方式 // 参数3 InvocationHandler h: 事件处理, 执行目标对象方法时, 会帮当前目标对象的方法作为参数传入 public Object getProxyInstance() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { // 每次调用目标对象方法时, 都会调用这个invoke, 所以可以从这加代理内容 // 如果有多个方法调用时, 每个方法加入什么内容, 从这个人理解可以增加 if,else 判断 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("JDK代理开始"); // 通过反射, 调用目标对象的方法 Object retureVal = method.invoke(target, args); System.out.println("JDK代理结束"); return retureVal; } }); } } package com.leon.design; public interface StarPlay { void play(); } package com.leon.design; public class SingerStar implements StarPlay{ private String singerName; @Override public void play() { System.out.println("I am " + singerName + ", i can sing."); } public SingerStar(String singerName) { this.singerName = singerName; } } package com.leon.design; public class DancerStar implements StarPlay{ private String dancerName; @Override public void play() { System.out.println("I am "+ dancerName +", I can dance."); } public DancerStar(String dancerName) { this.dancerName = dancerName; } }
还有一种叫做 Cglib 代理, 不需要实现接口, 就能实现代理. Cglib 包的底层时通过使用字节码处理框架 ASM 来转换字节码生成的新类.
装饰者模式
动态为一个对象增加一个新的功能, 被装饰新功能的方法, 可以先通过接口设计出来, 然后让被装饰的类和装饰类都实现这个接口.
关键是可以灵活的添加装饰对象.
角色:
接口/抽象类: Component,定义对象的方法
ConcreteComponent(被装饰类)(具体对象实现接口方法)
Decorator: 装饰器, 装饰的是ConcreteComponent的方法, 它含有一个被装饰的对象
举例: 星巴克 咖啡的问题: 有单品咖啡, 然后, 还有很多收费的调料, 比如加 牛奶, 加巧克力, 加草莓, 加冰块等等很多很多.
那么,如果有客户要加 2份巧克力+1份牛奶+1份草莓, 我们不可能根据客户的需求都创建一个新的类(排列组合,很多种可能), 那怎么办呢?
解决办法就是递归思维, 我们先用一种调料来“装饰”, 之后把装饰完之后的还继续当做是"单品咖啡",继续用milk 来装饰, 这样递归操作, 直到装饰完所有的调料.
举例中的对象:
abstract BasicCoffee (基础类)
private String description
private int cost
description() 方法, 介绍这杯咖啡的情况, 例如, 单品咖啡,加糖咖啡等
cost() // 具体计算咖啡的价格
ItalianCoffee(被装饰类) 单品意大利式咖啡, 继承 BasicCoffee
USACoffee(被装饰类) 单品美国咖啡, 继承 BasicCoffee
DecoratorCoffee(装饰器类) 用来装饰这个单品的, 持有单品的对象, 继承 BasicCoffee, 这个类可以定义为抽象类
MilkDecorator(牛奶作料) 给单品加牛奶, 继承 DecoratorCoffee, 这才是真正的具体装饰类
SugarDecorator(糖作料) 给单品加糖, DecoratorCoffee, 这才是真正的具体装饰类
ChocolatesDecorator(巧克力作料) 给单品加巧克力 DecoratorCoffee, 这才是真正的具体装饰类
现在需要2份巧克力 + 1份牛奶
package com.leon.design; public interface BaseCoffee { void getCurrentDescription(); int getCurrentCost(); } package com.leon.design; public class ItalianCoffee implements BaseCoffee{ private String description = "+ ItalianCoffee 1 cup"; private int cost = 20; @Override public int getCurrentCost() { return cost; } @Override public void getCurrentDescription() { System.out.print(this.description + ":price=" + cost + ". "); } } package com.leon.design; public class UASCoffee implements BaseCoffee{ private String description = "+ USACoffee 1 cup"; private int cost = 15; @Override public int getCurrentCost() { return cost; } @Override public void getCurrentDescription() { System.out.print(this.description + ":price=" + cost + ". "); } } package com.leon.design; public abstract class DecoratorCoffee implements BaseCoffee{ BaseCoffee basecoffee; public DecoratorCoffee(BaseCoffee basecoffee) { this.basecoffee = basecoffee; } @Override public abstract void getCurrentDescription(); @Override public abstract int getCurrentCost(); } package com.leon.design; public class MilkDecorator extends DecoratorCoffee{ private String description = "+ milk 1 time"; private int cost = 5; public MilkDecorator(BaseCoffee basecoffee) { super(basecoffee); } @Override public void getCurrentDescription() { basecoffee.getCurrentDescription(); System.out.print(this.description + ":price=" + this.getCurrentCost() + ". "); } @Override public int getCurrentCost() { return basecoffee.getCurrentCost() + this.cost; } } package com.leon.design; public class ChocolateDecorator extends DecoratorCoffee{ private String description = "+ Chocolate 1 time"; private int cost = 10; public ChocolateDecorator(BaseCoffee basecoffee) { super(basecoffee); } @Override public void getCurrentDescription() { basecoffee.getCurrentDescription(); System.out.print(this.description + ":price=" + this.getCurrentCost() + ". "); } @Override public int getCurrentCost() { return basecoffee.getCurrentCost() + this.cost; } } package com.leon.design; public class DecoratorClient { public static void main(String[] args) { BaseCoffee coffee = new ItalianCoffee(); // 放牛奶 1 次 coffee = new MilkDecorator(coffee); // 放牛奶 1 次 coffee = new MilkDecorator(coffee); // 放巧克力 1 次 coffee = new ChocolateDecorator(coffee); coffee.getCurrentDescription(); } }
在 JDK 的 IO 流的 InputStream 是一个抽象类, 这里边的继承关系, 就是一种装饰类.
外观模式(过程模式)
对 sub system中的一组接口提供一个外部界面(外观). 一种封装的思想. 一般情况下, 都会潜移默化的使用它.
家庭影院项目: 我们可以做一个 Facade(面板)类, 封装这个类的方法, 从而提供一个统一的接口给用户(ready, play, pause, end)
DVD 播放器(开,关,播放, 暂停)
投影仪(准备:插电源, 放画布 播放: 启动, 暂停:没有, 结束:拔电源, 收画布)
立体声音响(准备:插电源, 调整音量 播放: 启动, 暂停:声音暂停, 结束:拔电源)
在一组接口(ready, play, pause, end) 中分别调用子系统的对应方法.
(代码简单,不敲了, 直接画图把)