单例+责任链+装饰+代理
从看懂到看开——初探设计模式
本文档主要介绍了四种设计模式:单例模式、责任链模式、装饰模式和代理模式。
为什么选择这四种模式?因为设计模式是一种很玄的东西。作为初探,越看越迷糊,只能选择一些不那么迷糊的模式进行介绍,就当做抛砖引玉吧。
撰写本文档主要参考了《设计模式之禅》这本书,如有错漏还请告知。
1.单例模式
顾名思义,只有一个实例,而且自行实例化并向整个系统提供这个实例。
Ensure a class has only one instance, and provide a global point of access to it.
典型示例
首先是单例类。
public class SingleObject {
private static SingleObject instance = new SingleObject();
private SingleObject(){}
public static SingleObject getInstance(){
return instance;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
然后是场景类。
public class SingletonPatternDemo {
public static void main(String[] args) {
SingleObject object = SingleObject.getInstance();
object.showMessage();
}
}
优缺点分析
下面两段摘书,单例模式比较简单,有所了解可以跳过。
优点:
1.单例模式只有一个实例,减少了内存的开销,尤其是当一个对象需要频繁地创建、销毁且创建或销毁的性能又无法优化时,单例模式的优势就非常明显。
2.由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。
3.单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
4.单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
缺点:
1.没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
2.单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
3.单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。
小结:
单例模式就像双刃剑,优点是只有一个,缺点也是只有一个。
使用情景
1.要求生成唯一序列号的环境;
2.在整个项目中需要一个共享访问点或共享数据,例如spring configuration或一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
3.创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
4.需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式。
结合项目使用
MES的流程表单中有一个属性类型是自动生成的编码,那生成编码就可以做成单例的工具类。
2.责任链模式
使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.Chain the receiving objects and pass the request along the chain until an object handles it.
典型示例
目前的项目中都引入了log4j记录一些运行时信息,运行时信息被分成了不同的等级,比如error、info、debug等,此处便以此为蓝本,简化出一个日志记录器来描述责任链模式。
首先定义处理对象的基类和三种日志类型。
public abstract class AbstractLogger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;
protected int level;
protected AbstractLogger nextLogger;
public void setNextLogger(AbstractLogger nextLogger){
this.nextLogger = nextLogger;
}
public void logMessage(int level, String message){
if(this.level <= level){
write(message);
}
if(nextLogger !=null){
nextLogger.logMessage(level, message);
}
}
abstract protected void write(String message);
}
接下来是三种类型的实现类。
public class ConsoleLogger extends AbstractLogger {
public ConsoleLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Standard Console::Logger: " + message);
}
}
public class ErrorLogger extends AbstractLogger {
public ErrorLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Error Console::Logger: " + message);
}
}
public class FileLogger extends AbstractLogger {
public FileLogger(int level){
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("File::Logger: " + message);
}
}
最后是场景类。
public class ChainPatternDemo {
private static AbstractLogger getChainOfLoggers(){
AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);
return errorLogger;
}
public static void main(String[] args) {
AbstractLogger loggerChain = getChainOfLoggers();
loggerChain.logMessage(AbstractLogger.INFO,
"This is an information.");
loggerChain.logMessage(AbstractLogger.DEBUG,
"This is an debug level information.");
loggerChain.logMessage(AbstractLogger.ERROR,
"This is an error information.");
}
}
优缺点分析
优点:
1.将请求和处理解耦,请求者不必关心是谁处理的,处理者也不必对请求者知根知底。
2.灵活,可以动态调配处理的顺序,也很容易对处理类进行扩展。
缺点:
在链很长的时候性能堪忧,也不方便调试,因此要注意控制链的长度。
小结:
责任链模式看起来有点像js中的冒泡事件,也有点像一个膨胀的switch,case的数量不受限制。
使用情景
从定义中的请求、处理、返回结果这些词,很难不让人联想到Servlet中的FilterChain。比起这种从设计最初就比较容易想到的场景,作为补救措施出现的可能性也比较高,比如需求变更导致的同质业务增加。
结合项目使用
维修平台项目中,注册环节,制造商管理员和普通用户需要填写的内容不同,处理也稍有不同。可以通过责任链模式进行处理,注册统一接口,设置下一个处理对象,依次判断处理。
设置下一个处理对象的模式也不一定要通过显式的方式,也许可以通过链表结构来实现。
3.装饰模式
动态地给一个对象添加一些额外的职责,装饰模式比继承更灵活。
Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.
典型示例
装饰模式的结构稍微复杂一点,先上类图。

-
component是一个接口,也就是最原始的对象。 -
concrete component是component的具体实现。 -
decorator是装饰器。 -
concrete decorator是装饰器的具体实现。
下面以画图为例,描述装饰模式,首先是对象。
public interface Shape {
void draw();
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Rectangle");
}
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Circle");
}
}
然后是装饰器。
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
public void draw(){
decoratedShape.draw();
}
}
public class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
最后是场景类。
public class DecoratorPatternDemo {
public static void main(String[] args) {
Shape circle = new Circle();
Shape redCircle = new RedShapeDecorator(new Circle());
Shape redRectangle = new RedShapeDecorator(new Rectangle());
System.out.println("Circle with normal border");
circle.draw();
System.out.println("\nCircle of red border");
redCircle.draw();
System.out.println("\nRectangle of red border");
redRectangle.draw();
}
}
优缺点分析
优点:
对象和装饰器解耦,装饰器可以动态扩展,比较灵活。
缺点:
多层装饰是复杂的。因此要尽量减少装饰类的数量,以便降低系统的复杂度。
小结:
1.装饰模式是继承的一种替代方案,子类无限扩充是灾难,装饰太多也是。
2.继承是静态的,装饰是动态的。在某些情况下,装饰更加易于维护。
使用情景
就像定义提到的,继承可以使用的地方,都可以考虑是否装饰模式更合适。
比如需要动态添加或撤销功能的时候,如果是在继承关系中要拆掉中间层的封装,不改动对象基本上不可能,而装饰器就比较灵活,在场景类中去掉就可以。
类似模式对比
参见第四小节代理模式的模式对比。
结合项目使用
也许表单属性定义的继承实现可以用装饰模式替代。
4.代理模式(委托模式)
为其他对象提供一种代理以控制对这个对象的访问。
Provide a surrogate or placeholder for another object to control access to it.
典型示例
代理模式的结构稍微复杂一点,先上类图。

-
Subject无特殊要求,就是对象需要实现的功能。 -
RealSubject是实现了Subject接口。 -
Proxy是代理类,在代理类中可进行预处理和善后工作。
下面以图片加载为例,描述代理模式,首先是加载图片的接口和实现。
public interface Image {
void display();
}
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
然后是代理类,代理类也要实现功能。
public class ProxyImage implements Image{
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName){
this.fileName = fileName;
}
@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}
最后是场景类。
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
image.display();
System.out.println("");
image.display();
}
}
第一次需要从磁盘加载,第二次就不需要了。(这个实例没有书中代练游戏的场景那么生动,有兴趣的可以看书。)
优缺点分析
优点:
1.职责清晰
真实的实现类就负责主要功能的实现,代理类则处理一下辅助功能。比如打游戏是主要功能,而结算游戏升级费用就是辅助功能。
2.高扩展性
真实的实现类在实际场景下难免发生变更,但是只要接口没有变更,代理类就无需修改。
3.智能化
在上一小结的例子中看不出来智能化在哪里,这一特点具体体现在“动态代理”的实现中。本文档对代理模式的扩展不做赘述,感兴趣的可以看书。
缺点:
有些类型的代理模式可能会造成请求的处理速度变慢;有些代理模式的实现非常复杂。
小结:
1.代理模式就是对目标方法进行拦截(控制),然后在目标方法的前后加塞其他功能(增强)。
2.代理模式有诸多扩展,看书的体会就是既视感很强,spring到底用了多少模式。
使用情景
1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护代理。 5、Cache代理。 6、防火墙代理。 7、同步化代理。 8、智能引用代理。
目前项目中已经用到的比如Spring AOP,利用切面做消息通知。
类似模式对比
与第三小节的装饰模式进行对比,两者的概念其实有些类似,但是从类图中可以看到,代理模式更加强势,它决定了实际对象执行或者不执行,装饰器就只是增强,代理更多的是控制。
结合项目使用
利用现有的AOP做日志或消息通知。
5.小结
coding不易,架构更不易,且行且珍惜。