设计模式-职责型模式

in 四月桐花 with 0 comment

导语

我们习惯去说类和方法承担着各种各样的职责。事实上,这通常意味着你有责任提供健壮的设计,并让代码承担合适的功能。幸运的是,Java语言分担了一部分责任。我们可以限制类、字段和方法的可见性,从而去限制其他开发人员对你开发的代码的调用。可见性向读者展示了该如何暴露类的部分内容。

单例模式-Singleton

有两种场景较为适合单例模式的使用:

  1. 当你需要一个全局唯一的对象去处理业务的时候,此时可以通过单例阻塞其他调用
  2. 当系统实例化了大量不必要的对象时

懒汉与饿汉模式的区别主要在于使用场景上,如果当前单例对象的初始化不依赖与其他资源的加载,则可以使用饿汉方式提前加载实例,但如果单例对象的初始化存在前置条件,则需要使用懒汉模式,并在预处理完毕时,初始化这个单例对象

观察者模式-Observer

理解Observer的核心在于监听(观察)和通知,创建一个主题(Subject),然后将主题添加到**监听(观察者)**中,同时主题也会持有所有添加了它的监听的引用(通常会维护一个List保存所有的观察者).此时Subject若发生变化(或执行动作),则可以通过它持有的观察者的引用,通知到观察者们.

P.S.Observer有两个要点需要注意:1.直接观察者,间接观察者过多会导致程序复杂度上升,甚至循环依赖的情况;2.过多的观察者在执行业务的时候,会导致运行效率问题(例如每个观察者都要发起一个HTTP请求,此时由于程序是顺序执行,会导致系统延迟),需要考虑并发处理通知

调停者模式-Mediator

理解mediator的关键在于要解决什么样的问题,例如转账行为,当A要给B转账的时候,面向对象会要求A持有B的引用,除了A->B,还可能有B->A,A<->C,B<->D...这个时候,你需要有一个交易平台处理这些转账的业务.这个平台对象,就是调停者.需要注意的是A,B,C...不一定是同一个抽象概念,笼统地将如果你的业务存在大量集合之间的交互<->,那么你就需要一个mediator维护这些对象之间的关系.

P.S.mediator模式会导致大量的对象和业务放在一个类(mediator类)中处理,此时需要格外注意这些实体职能的区分,暴露需要交互的方法,同时将其他业务下沉,尽可能使调停者负责的功能更纯粹.

Mediator

代理模式-Proxy

Proxy是一个使用场景不太明显的模式,通常当你需要隐藏或通过添加权限的方式限制某些类的使用权限市,可以使用代理模式.
代理模式的核心在于限制真实对象的使用权限
P.S.对于适配器模式,门面模式,代理模式可以放在一起理解,而这种理解的基础,并不是建立在面向对象的UML结构上,而是要建立在它们的适用场景上.
适配器模式:通过接口封装,适配Client的需求
门面模式:将众多子系统的功能,封装(组合)到一个门面对象中,供Client使用
代理模式:将真实的使用类隐藏,通过代理对象,附加其他功能(如有没有权限,需要调用具体那个类的实例等)

责任链模式-Chain of Responsibility

责任链模式的适用环境有一个明显的特征:即需要对同一个对象(或同一组数据),执行多次的操作,例如消息的处理,可以拆分成:屏蔽敏感词,处理特殊符号,处理表情,屏蔽安全指令...又如JavaWeb中的Filter过滤器:解析Http协议,处理编码格式...

责任链模式的特征在于,超类(接口)定义了必要的抽象行为和必要的限制(如不同子类的执行顺序,条件等),子类负责实现这些抽象行为.需要注意的是:

  1. 责任链主要简化了调用端的复杂度,你可以将任意需要执行的逻辑,加入到责任链的执行环节,而无需关注他的内部
  2. 责任链的子类,如果有必要顺序要求,可以通过持有兄弟类引用的方式,保证自身的执行顺序
  3. 责任链并不保证每个环节都一定会执行,同时由于复杂度的提高,还可能出现同一个子类反复调用的情况

P.S.提到责任链会让我联想起workflow(工作流),但workflow和chain of responsibility并非一个层面的问题,工作流是更高层面的实现,并且未必会用到责任链模式,两者仅在抽象结构上有一些类似而已.

享元模式-Flyweight

Flyweight用于处理系统频繁大量创建对象的场景,最典型的应用就是String对象初始化的逻辑,对于String类型的初始化:

  1. 当初始化一个String字符串时,会先查看内存中是否存在这个字符串
  2. 内存中不存在时,将会分配一段空间存储当前的字符串
  3. 如果存在,则后续的引用将会直接使用这段内存的数据,而非创建一段有着相同内容的对象

当然,String还有一些其他的优化,类似的还有数据库连接池的逻辑(连接池中会常驻一部分长连接,当有CURD请求到达时,将会直接使用连接池中已经建立好连接的对象,而非创建新的连接),这样做的好处是能够有效的减少每次创建tcp连接所浪费的时间.

对于Java端的实现,关键在于:

  1. 享元空间,通常是一个Map结构
  2. 工厂对象,通过工厂对象对要实例化的对象进行管理

P.S.需要格外注意的是享元模式要区分出哪些属性是可以共享,哪些属性是不可以共享的,因为一旦出现混乱,将导致共用属性被非法修改,或是创建了大量非必要的公共属性.

小结

文章不再对具体的案例做分析,重点在于指出这些设计模式的核心特征和适用范围.从分类(职责型模式)的角度上来看: