代理模式(Proxy)
代理模式的原理非常简单,它在不改变原始类的情况下,通过引入代理类来给原始类附加功能。它最常用的场景就是为业务系统开发非功能性需求,比如:监控,统计,鉴权,限流,事务,日志等。
假设我们有如下所示的一个接口统计模块。
Metrics metrics = new Metrics();long startTimestamp=System.currentTimeMillis();//执行业务long endTimestamp=System.currentTimeMillis();RequestInfo info=new RequestInfo();info.setApiName("api");info.setTimestamp(startTimestamp);info.setResponseTime(endTimestamp-startTimestamp);metrics.collect(info);
如果在接口中直接使用,统计模块的代码会侵入到业务代码中与业务代码高度耦合,如果未来需要替换接口统计模块,那么替换成本会比较大。而且统计模块与业务无关,本身就不应该放到一个类中,业务类应当只聚焦业务处理。
为了将模块代码与业务代码解耦,代理模式就派上用场了,如下所示。
class UserVo{}interface IUserController{UserVo login(String telephone,String password);UserVo register(String telephone,String password);}class UserController implements IUserController{@Overridepublic UserVo login(String telephone, String password) {UserVo userVo=new UserVo();//业务逻辑return userVo;}@Overridepublic UserVo register(String telephone, String password) {UserVo userVo=new UserVo();//业务逻辑return userVo;}}class UserControllerProxy implements IUserController{//依赖注入private Metrics metrics;private UserController userController;@Overridepublic UserVo login(String telephone, String password) {//统计模块代码UserVo userVo=userController.login(telephone,password);//统计模块代码return userVo;}@Overridepublic UserVo register(String telephone, String password) {//统计模块代码UserVo userVo=userController.register(telephone,password);//统计模块代码return userVo;}}
这时候只需要调用UserControllerProxy中的login()方法和register()方法即可。当业务逻辑发生变化时只需要修改UserController即可,统计模块更换时只需要修改UserControllerProxy。
上面的代码采用基于接口而非实现的编程思想,如果我不想加一个接口,或者原始类代码并不是我们自己维护的,这时候就没法再使用基于接口的思想了。我们可以通过继承原始类来对原始类进行扩展,如下所示。
class UserVo{}class UserController{public UserVo login(String telephone, String password) {UserVo userVo=new UserVo();//业务逻辑return userVo;}public UserVo register(String telephone, String password) {UserVo userVo=new UserVo();//业务逻辑return userVo;}}class UserControllerProxy extends UserController{//依赖注入private Metrics metrics;private UserController userController;@Overridepublic UserVo login(String telephone, String password) {//统计模块代码UserVo userVo=userController.login(telephone,password);//统计模块代码return userVo;}@Overridepublic UserVo register(String telephone, String password) {//统计模块代码UserVo userVo=userController.register(telephone,password);//统计模块代码return userVo;}}
通过代理模式我们将第三方模块与业务解耦了,但是同样还有一些问题。一方面我们要将原始类中所有的方法都重新实现一遍,另一方面每一个原始类我们都需要为其创建一个代理类。对于Java,我们可以使用动态代理来解决这个问题。
class MetricsCollectorProxy{private final Metrics metrics;//依赖注入public MetricsCollectorProxy(Metrics metrics){this.metrics=metrics;}public Object createProxy(Object proxiedObject){return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(),proxiedObject.getClass().getInterfaces(),new DynamicProxyHandler(proxiedObject));}private static class DynamicProxyHandler implements InvocationHandler{private final Object proxiedObject;public DynamicProxyHandler(Object proxiedObject){this.proxiedObject=proxiedObject;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long startTimestamp=System.currentTimeMillis();Object result=method.invoke(proxiedObject,args);//执行业务代码long endTimestamp=System.currentTimeMillis();String apiName=proxiedObject.getClass().getName()+":"+method.getName();RequestInfo info=new RequestInfo();info.setApiName(apiName);info.setTimestamp(startTimestamp);info.setResponseTime(endTimestamp-startTimestamp);metrics.collect(info);return result;//返回业务代码的返回值}}}
需要注意的是,动态代理的原始类是一定要实现接口的,并且代理实例转型时只能转型为接口,否则会抛出ClassCastException。
一个简单的代理类如下所示。
class AnyProxy{public Object createProxy(Object proxiedObject){return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(),proxiedObject.getClass().getInterfaces(),new DynamicProxyHandler(proxiedObject));}private static class DynamicProxyHandler implements InvocationHandler{private final Object proxiedObject;public DynamicProxyHandler(Object proxiedObject){this.proxiedObject=proxiedObject;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("before");Object result=method.invoke(proxiedObject,args);System.out.println("after");return result;}}}
同时定义了如下所示的原始类和接口。
interface IUserController{Object login(String tel,String password);}class UserController implements IUserController{@Overridepublic Object login(String telephone,String password) {System.out.println("execute task: telephone="+telephone+",password="+password);return "ok";}}
使用方式如下。
IUserController controller= (IUserController) new AnyProxy().createProxy(new UserController());System.out.println(controller.login("110","password"));/*** 下面为输出*/beforeexecute task: telephone=110,password=passwordafterok
桥接模式(Bridge)
在《设计模式》一书中对桥接模式的解释是“将抽象和实现解耦,让它们可以独立变化。”这句话一听会让人感到疑惑,在很多书籍中对桥接模式还有另一种理解,即“一个类存在两个(或多个)独立变化的维度,我们可以通过组合的方式让它们可以独立进行扩展。”通过组合来代替进程关系,避免继承层次爆炸。我们来看一个例子:根据不同的告警规则,触发不同类型的告警。告警支持多种通知渠道,包括:邮件,短信等。通知的紧急程度有多个级别,包括:严重,普通等。
理解了需求之后得到如下代码。其中,Notification类相当于抽象,MsgSender相当于实现,两者可以独立开发,通过组合关系(即桥梁)可以任意组合在一起。MsgSender的变化不会影响到Notification,并且MsgSender和Notification之间的组合关系可以通过配置文件动态地去指定。
interface MsgSender{boolean send(String msg);}class TelephoneMsgSender implements MsgSender{private final List<String> telephones;public TelephoneMsgSender(List<String> telephones){this.telephones=telephones;}@Overridepublic boolean send(String msg) {//发送消息代码System.out.println("send msg:"+msg+" by telephone");return true;}}class EmailMsgSender implements MsgSender{private final List<String> emails;public EmailMsgSender(List<String> emails){this.emails=emails;}@Overridepublic boolean send(String msg) {//发送邮件代码System.out.println("send msg:"+msg+" by email");return true;}}abstract class Notification{protected final MsgSender sender;public Notification(MsgSender sender){this.sender=sender;}public abstract boolean notify(String msg);}class SevereNotification extends Notification{public SevereNotification(MsgSender sender) {super(sender);}@Overridepublic boolean notify(String msg) {System.out.println("severe notification");return sender.send(msg);}}class NormalNotification extends Notification{public NormalNotification(MsgSender sender) {super(sender);}@Overridepublic boolean notify(String msg) {System.out.println("normal notification");return sender.send(msg);}}
装饰器模式(Decorator)
装饰器模式的作用是对原始类进行增强,为原始类附加更多的功能,听起来与代理模式很像。与代理模式不同的是,代理模式是为原始类附加无关的功能,而装饰器模式附加的功能与原始类是相关的。我们以一个灯的功能增强作为示例。
首先我们有一个普通的灯,没有任何特性,它就是一颗纯粹的灯。现在按照灯的发光原理分类,可以分为氙气灯和LED;按照射程分类可以分为远光灯和近光灯。我们希望将它们组合起来得到一颗LED的远光灯,LED的近光灯,氙气的远光灯······或则我们只要一颗LED,不要它区分远光灯还是近光灯。这时候我们可以使用装饰器模式来实现,代码如下所示。
class Light{void illuminate(){System.out.println("illuminate");}}class LowBeam extends Light{private final Light light;public LowBeam(Light light){this.light=light;}@Overridepublic void illuminate() {System.out.println("low beam");light.illuminate();}}class HighBeam extends Light{private final Light light;public HighBeam(Light light){this.light=light;}@Overridevoid illuminate() {System.out.println("high beam");light.illuminate();}}class XenonLamp extends Light{private final Light light;public XenonLamp(Light light){this.light=light;}@Overridevoid illuminate() {System.out.println("xenon lamp");light.illuminate();}}class LED extends Light{private final Light light;public LED(Light light){this.light=light;}@Overridevoid illuminate() {System.out.println("LED");light.illuminate();}}
如果我们想要得到一颗LED的远光灯可以这样组合。
new HighBeam(new LED(new Light())).illuminate();/*** 输出如下*/high beamxenon lampilluminate
实际上Java IO库就使用了装饰器模式。
File file=new File("map.data");ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(file));
适配器模式(Adapter)
顾名思义,适配器模式的作用就是用来做适配的。适配器模式可以用来补救设计上的缺陷,通过封装使得原本不兼容的接口兼容。或者通过封装使得接口更加优雅,具体例子如下所示。
class Any{public static void staticFunction(){}public void uglyNameFunction(){}}interface ITarget{void fixStaticFunction();void fixUglyNameFunction();}class AnyAdapter implements ITarget{private final Any any;public AnyAdapter(Any any){this.any=any;}@Overridepublic void fixStaticFunction() {Any.staticFunction();}@Overridepublic void fixUglyNameFunction() {any.uglyNameFunction();}}
通过AnyAdapter封装后,抽象出了更好的接口设计。将原本会影响到代码可测试性的静态方法封装到了成员方法中,同时原本命名不合理的函数也得到了重新封装。
外观模式(Facade)
外观模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。举一个例子,假设系统提供了a,b,c,d四个接口,客户端需要调用系统的a,b,d接口。为了调用更加方便,我们可以提供一个包裹a,b,d接口的外观接口x给客户端使用。
组合模式(Composite)
组合模式就是将一组对象组织成树形结构,以表示一种“部分-整体”的层次结构。
享元模式(Flyweight)
享元,顾名思义就是被共享的单元,使用享元模式的目的就是复用对象,节约内存,当然前提是享元对象是不可变对象。因为对象是不可变的,所以一旦通过构造函数初始化完成之后,他的成员变量或属性就不会再被修改了,所以我们不能暴露setter()方法。
如下所示是一个简易的文本编辑器。
class Character{private char c;private Font font;private int size;private int colorRGB;public Character(char c,Font font,int size,int colorRGB){this.c=c;this.font=font;this.size=size;this.colorRGB=colorRGB;}public enum Font{微软雅黑}}class Editor{private List<Character> chars=new ArrayList<>();public void appendCharacter(char c, Character.Font font,int size,int colorRGB){chars.add(new Character(c,font,size,colorRGB));}}
每次输入文字都会向Editor中添加一个Character对象,如果一个文本中有上万甚至几十万字,那么我们就需要在内存中存储这么多Character对象。假设有十万个字,那么在64位JVM上存储这么多对象需要的空间大小为((8+4+(1+(8+4+4)+4+4)+3)100000)/(10242024)=3.81GB,可见内存消耗非常之大,那么有没有什么办法可以减少内存消耗呢?我们可以通过享元模式共享不变的相同单元以达到节省内存的目的。
class CharacterStyle{private Font font;private int size;private int colorRGB;public enum Font{yahei;}public CharacterStyle(Font font,int size,int colorRGB){this.font=font;this.size=size;this.colorRGB=colorRGB;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;CharacterStyle that = (CharacterStyle) o;return size == that.size &&colorRGB == that.colorRGB &&font == that.font;}@Overridepublic int hashCode() {return Objects.hash(font, size, colorRGB);}}class CharacterStyleFactory{private static final Map<Integer,CharacterStyle> styles=new HashMap<>();public static CharacterStyle getStyle(CharacterStyle.Font font,int size,int colorRGB){CharacterStyle newStyle=new CharacterStyle(font,size,colorRGB);int hash=newStyle.hashCode();styles.putIfAbsent(hash,newStyle);return styles.get(hash);}}class Character{private char c;CharacterStyle style;public Character(char c,CharacterStyle style){this.c=c;this.style=style;}}
在一个文本文件中,用到的字体格式并不会太多,所以我们可将字体格式设计为享元,让不同的文字共享使用,这样一来便节省了内存。
