设计模式之装饰者模式及观察者模式
装饰器模式:
装饰器模式(Decorator Pattern)(包装)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式。
应用实例: 1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
由于装饰者模式是一种特殊的适配器模式的变种,这里沿用适配器模式中用到的例子,及登陆业务的拓展,第三方登陆的案例进行演示。
先定义一个service模拟线上已定的稳定到不可变的登录注册业务的接口跟实现:
public interface ISigninService { //注册方法 public ResultMsg regist(String username,String password); // 登录的方法 public ResultMsg login(String username,String password); } public class SiginService implements ISigninService { /** * 注册方法 * @param username * @param password * @return */ public ResultMsg regist(String username, String password){ return new ResultMsg("200","注册成功",new Member()); } /** * 登录的方法 * @param username * @param password * @return */ public ResultMsg login(String username,String password){ return null; } }
其实涉及的 Menber 人员实体类及 ResultMsg 返回信息如下:
public class Member { private String username; private String password; // 唯一编号 private String mid; // 具体信息 private String info; //为节省篇幅,这里删除了set跟get方法 } public class ResultMsg { //返回编码 private String code; // 返回信息 private String msg; //返回数据 private Object data; public ResultMsg(String code, String msg, Object data) { this.code = code; this.msg = msg; this.data = data; } //为节省篇幅,这里删除了set跟get方法 }
对于适配器模式更注重的是兼容,所以采取的方案是对原有固定的业务实现类进行一个拓展继承,这样既保留了原来的业务,也拓展了新业务,适配者和被适配者没有必然的层级联系,通常采用代理或者继承形式进行包装。
而在装饰者模式中注重的是覆盖、扩展。比如Spring中的 IO流的包装,所采用的就是这个模式。装饰者和被装饰者都要实现同一个接口,主要目的是为了扩展,依旧保留OOP关系(同宗同源)。
所以这里采用装饰者模式的话,需要装饰器类也去实现 ISigninService 接口,我这里先拓展一下接口实现,再定义一个装饰器类去实现新的接口:
public interface ISigninForThirdService extends ISigninService { public ResultMsg loginForQQ(String openId); public ResultMsg loginForWechat(String openId); public ResultMsg loginForTelphone(String telphone,String code); public ResultMsg loginForRegist(String username,String password); }
装饰器类:
public class SigninForThirdService implements ISigninForThirdService { private ISigninService service; public SigninForThirdService(ISigninService service){ this.service = service; } @Override public ResultMsg regist(String username, String password) { return service.regist(username,password); } @Override public ResultMsg login(String username, String password) { return service.login(username,password); } public ResultMsg loginForQQ(String openId){ //1、openId是全局唯一,我们可以把它当做是一个用户名(加长) //2、密码默认为QQ_EMPTY //3、注册(在原有系统里面创建一个用户) //4、调用原来的登录方法 return loginForRegist(openId,null); } public ResultMsg loginForWechat(String openId){ return null; } public ResultMsg loginForTelphone(String telphone,String code){ return null; } public ResultMsg loginForRegist(String username,String password){ this.regist(username,null); return this.login(username,null); } }
测试:运行以下代码即可以实现不影响既定业务的情况下进行业务的拓展。
public class SigginTest { public static void main(String[] args) { //原来的功能依旧对外开放,依旧保留 //新的功能同样的也可以使用 ISigninForThirdService signinForThirdService = new SigninForThirdService(new SiginService()); signinForThirdService.loginForQQ("xxssdsd"); } }
观察者模式:
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。3.Spring 中 Observer 模式常用的地方是 Listener 的实现。如 ApplicationListener。
使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
当一个用户下单完支付成功之后,客服MM就应当通知仓库发货,姑且把客服MM当成一个观察者(Observer)的角色,就利用这么一个场景去做一个演示,基于这个案例,我们需要一个监听器类(EventLisenter),可以提供注册事件及触发事件的方法,还需要一个事件类(Event)。然后需要有一个事件源类,即由谁去触发这个支付的动作继而触发观察者事件,我们这里即收银台名为这里用Subject 类表示。
先来看事件类的定义:
public class Event { //事件源 private Object source; //通知目标 private Object target; //回调 private Method callback; //触发 private String trigger; //触发时间 private long time; public Event(Object target, Method callback) { this.target = target; this.callback = callback; } // 重写一下toString 一边输出观察 @Override public String toString() { return "Event{" + "\n\tsource=" + source + ",\n" + "\ttarget=" + target + ",\n" + "\tcallback=" + callback + ",\n" + "\ttrigger='" + trigger + '\'' + "\n" + '}'; } public Object getSource() { return source;} public Object getTarget() {return target;} public void setTarget(Object target) {this.target = target;} public Method getCallback() {return callback;} public void setCallback(Method callback) {this.callback = callback;} public String getTrigger() {return trigger;} public long getTime() { return time;} Event setTime(long time) { this.time = time; return this; } Event setSource(Object source) { this.source = source; return this; } Event setTrigger(String trigger) { this.trigger = trigger; return this; } }
然后定义事件监听器类:
public class EventLisenter { //Map相当于是一个注册器 protected Map<Enum,Event> events = new HashMap<Enum,Event>(); public void addLisenter(Enum eventType, Object target, Method callback){ //注册事件 //用反射调用这个方法 events.put(eventType,new Event(target,callback)); } private void trigger(Event e){ e.setSource(this);//设置事件源 e.setTime(System.currentTimeMillis()); try { //反射调用该方法 e.getCallback().invoke(e.getTarget(),e); } catch (Exception e1) { e1.printStackTrace(); } } protected void trigger(Enum call){ // 看是否注册了该事件 if(!this.events.containsKey(call)){ return ;} //注册则触发 trigger(this.events.get(call).setTrigger(call.toString())); } }
这个的事件的类型用一个枚举类来表示:
public enum SubjectEventType { ON_PAY,//支付事件 ON_CANCEL;//取消订单事件 }
事件源收银台Subject类通过继承监听器类来实现实现的触发:
public class Subject extends EventLisenter { //通常的话,采用动态代理来实现监控,避免了代码侵入 public void pay(){ System.out.println("调用支付的方法"); //触发事件 trigger(SubjectEventType.ON_PAY); } public void cancel(){ System.out.println("调用取消订单的方法"); trigger(SubjectEventType.ON_CANCEL); } }
最后来看看观察者的定义:
public class Observer { public void advice(Event e){ System.out.println("===触发"+e.getTrigger()+"事件,打印日志===\n" + e); } }
测试:运行一下代码可以发现事件触发观察者方法执行:
public class ObserverTest { public static void main(String[] args) { try{ //观察者 Observer observer = new Observer(); //观察者的具体动作 Method advice = Observer.class.getMethod("advice", new Class<?>[]{Event.class}); //收银台调用了支付或者取消订单 Subject subject = new Subject(); subject.addLisenter(SubjectEventType.ON_PAY,observer,advice); // 具体的执行方法可以自己定义,触发不同事件执行不同的方法,也可以统一处理 // subject.addLisenter(SubjectEventType.ON_CANCEL,observer,advice); subject.pay(); // subject.cancel(); }catch (Exception e){ e.printStackTrace(); } } }