本文为原创博文,转载请注明出处,侵权必究!

  1、初识装饰器模式

    装饰器模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能。其结构图如下:

      

    • Component为统一接口,也是装饰类和被装饰类的基本类型。
    • ConcreteComponent为具体实现类,也是被装饰类,他本身是个具有一些功能的完整的类。
    • Decorator是装饰类,实现了Component接口的同时还在内部维护了一个ConcreteComponent的实例,并可以通过构造函数初始化。而Decorator本身,通常采用默认实现,他的存在仅仅是一个声明:我要生产出一些用于装饰的子类了。而其子类才是赋有具体装饰效果的装饰产品类。
    • ConcreteDecorator是具体的装饰产品类,每一种装饰产品都具有特定的装饰效果。可以通过构造器声明装饰哪种类型的ConcreteComponent,从而对其进行装饰。

  2、最简单的代码实现装饰器模式

//基础接口
public interface Component { public void biu();
}
//具体实现类
public class ConcretComponent implements Component { public void biu() { System.out.println("biubiubiu");
}
}
//装饰类
public class Decorator implements Component { public Component component; public Decorator(Component component) { this.component = component;
} public void biu() { this.component.biu();
}
}
//具体装饰类
public class ConcreteDecorator extends Decorator { public ConcreteDecorator(Component component) { super(component);
} public void biu() { System.out.println("ready?go!");
this.component.biu();
}
}

    这样一个基本的装饰器体系就出来了,当我们想让Component在打印之前都有一个ready?go!的提示时,就可以使用ConcreteDecorator类了。具体方式如下:

  //使用装饰器
  Component component = new ConcreteDecorator(new ConcretComponent());
  component.biu();   //console:
  ready?go!
  biubiubiu

  3、为何使用装饰器模式

    一个设计模式的出现一定有他特殊的价值。仅仅看见上面的结构图你可能会想,为何要兜这么一圈来实现?仅仅是想要多一行输出,我直接继承ConcretComponent,或者直接在另一个Component的实现类中实现不是一样吗?

    首先,装饰器的价值在于装饰,他并不影响被装饰类本身的核心功能。在一个继承的体系中,子类通常是互斥的。比如一辆车,品牌只能要么是奥迪、要么是宝马,不可能同时属于奥迪和宝马,而品牌也是一辆车本身的重要属性特征。但当你想要给汽车喷漆,换坐垫,或者更换音响时,这些功能是互相可能兼容的,并且他们的存在不会影响车的核心属性:那就是他是一辆什么车。这时你就可以定义一个装饰器:喷了漆的车。不管他装饰的车是宝马还是奥迪,他的喷漆效果都可以实现。

    再回到这个例子中,我们看到的仅仅是一个ConcreteComponent类。在复杂的大型项目中,同一级下的兄弟类通常有很多。当你有五个甚至十个ConcreteComponent时,再想要为每个类都加上“ready?go!”的效果,就要写出五个子类了。毫无疑问这是不合理的。装饰器模式在不影响各个ConcreteComponent核心价值的同时,添加了他特有的装饰效果,具备非常好的通用性,这也是他存在的最大价值。

  4、实战中使用装饰器模式

    写这篇博客的初衷也是恰好在工作中使用到了这个模式,觉得非常好用。需求大致是这样:采用sls服务监控项目日志,以Json的格式解析,所以需要将项目中的日志封装成json格式再打印。现有的日志体系采用了log4j + slf4j框架搭建而成。调用起来是这样的:

  private static final Logger logger = LoggerFactory.getLogger(Component.class);
  logger.error(string);

    这样打印出来的是毫无规范的一行行字符串。在考虑将其转换成json格式时,我采用了装饰器模式。目前有的是统一接口Logger和其具体实现类,我要加的就是一个装饰类和真正封装成Json格式的装饰产品类。具体实现代码如下:

/**
* logger decorator for other extension
* this class have no specific implementation
* just for a decorator definition
* @author jzb
*
*/
public class DecoratorLogger implements Logger {

public Logger logger; public DecoratorLogger(Logger logger) { this.logger = logger;
}
    
@Override
public void error(String str) {} @Override
public void info(String str) {} //省略其他默认实现
}
/**
* json logger for formatted output
* @author jzb
*
*/
public class JsonLogger extends DecoratorLogger {
public JsonLogger(Logger logger) { super(logger);
} @Override
public void info(String msg) { JSONObject result = composeBasicJsonResult();
result.put("MESSAGE", msg);
logger.info(result.toString());
} @Override
public void error(String msg) { JSONObject result = composeBasicJsonResult();
result.put("MESSAGE", msg);
logger.error(result.toString());
} public void error(Exception e) { JSONObject result = composeBasicJsonResult();
result.put("EXCEPTION", e.getClass().getName());
String exceptionStackTrace = ExceptionUtils.getStackTrace(e);
result.put("STACKTRACE", exceptionStackTrace);
logger.error(result.toString());
} public static class JsonLoggerFactory { @SuppressWarnings("rawtypes")
public static JsonLogger getLogger(Class clazz) { Logger logger = LoggerFactory.getLogger(clazz);
return new JsonLogger(logger);
}
} private JSONObject composeBasicJsonResult() {
//拼装了一些运行时信息
}
}

    可以看到,在JsonLogger中,对于Logger的各种接口,我都用JsonObject对象进行一层封装。在打印的时候,最终还是调用原生接口logger.error(string),只是这个string参数已经被我们装饰过了。如果有额外的需求,我们也可以再写一个函数去实现。比如error(Exception e),只传入一个异常对象,这样在调用时就非常方便了。

    另外,为了在新老交替的过程中尽量不改变太多的代码和使用方式。我又在JsonLogger中加入了一个内部的工厂类JsonLoggerFactory(这个类转移到DecoratorLogger中可能更好一些),他包含一个静态方法,用于提供对应的JsonLogger实例。最终在新的日志体系中,使用方式如下:

    private static final Logger logger = JsonLoggerFactory.getLogger(Component.class);
logger.error(string);

    他唯一与原先不同的地方,就是LoggerFactory -> JsonLoggerFactory,这样的实现,也会被更快更方便的被其他开发者接受和习惯。

装饰器模式(Decorator)——深入理解与实战应用的更多相关文章

  1. 设计模式(八)装饰器模式Decorator(结构型)

    设计模式(八)装饰器模式Decorator(结构型) 1. 概述 若你从事过面向对象开发,实现给一个类或对象增加行为,使用继承机制,这是所有面向对象语言的一个基本特性.如果已经存在的一个类缺少某些方法 ...

  2. python 设计模式之装饰器模式 Decorator Pattern

    #写在前面 已经有一个礼拜多没写博客了,因为沉醉在了<妙味>这部小说里,里面讲的是一个厨师苏秒的故事.现实中大部分人不会有她的天分.我喜欢她的性格:总是想着去解决问题,好像从来没有怨天尤人 ...

  3. 【PHP设计模式 09_ZhuangShiQi.php】装饰器模式 (decorator)

    <?php /** * [装饰器模式 (decorator)] * 有时候发布一篇文章需要经过很多人手,层层处理 */ header("Content-type: text/html; ...

  4. 装饰器模式-Decorator(Java实现)

    装饰器模式-Decorator(Java实现) 装饰器模式允许向一个现有的对象添加新的功能, 同时又不改变其结构. 其中 "现有对象"在本文中是StringDisplay类. 添加 ...

  5. 用最简单的例子理解装饰器模式(Decorator Pattern)

    假设有一个公司要做产品套餐,即把不同的产品组合在一起,不同的组合对应不同的价格.最终呈现出来的效果是:把产品组合的所有元素呈现出来,并显示该组合的价格. 每个产品都有名称和价格,首先设计一个关于产品的 ...

  6. 装饰器模式 Decorator 结构型 设计模式 (十)

    引子           现实世界的装饰器模式 大家应该都吃过手抓饼,本文装饰器模式以手抓饼为模型展开简介 "老板,来一个手抓饼,  加个培根,  加个鸡蛋,多少钱?" 这句话会不 ...

  7. 说说设计模式~装饰器模式(Decorator)~多功能消息组件的实现

    返回目录 为何要设计多功能消息组件 之前写过一篇装饰器模式的文章,感觉不够深入,这次的例子是实现项目中遇到的,所以把它拿出来,再写写,之前也写过消息组件的文章,主要采用了策略模式实现的,即每个项目可以 ...

  8. 装饰器模式(Decorator)

    转自http://blog.csdn.net/hust_is_lcd/article/details/7884320 1.认识装饰器模式 装饰模式能够实现动态的为对象添加功能,是从一个对象外部来给对象 ...

  9. 设计模式入门之装饰器模式Decorator

    //装饰模式定义:动态地给一个对象加入一些额外的职责. //就添加功能来说.装饰模式比生成子类更为灵活 //这也提现了面向对象设计中的一条基本原则,即:尽量使用对象组合,而不是对象继承 //Compo ...

随机推荐

  1. jQuery后续...

    jQuery 选择器 1.jQuery选择器的简介 (1). Jquery中的选择器完全继承了CSS的风格,利用Jquery选择器,可以非常便捷和快速的找出特定的Dom元素,然后为他们添加相应的行为, ...

  2. JavaScript 图片轮播入门

    轮播要求:一个在页面居中的矩形框,图片依次从矩形框中经过 当图片完整占满矩形框时 停留一小段时间再向左边移动经过矩形框的图片自动跑到右边最后一个图的后面.核心原理:在一个for循环中利用offsetl ...

  3. ThinkPhp模板转Flask模板

    Template Converter 网上的PHP资源很多,项目要用Python,所以想起做一个模板转换器,从ThinkPhp转成Flask的Jinja模板. 直接指定两个目录,将目录下的模板文件转换 ...

  4. 快速排序 partition函数的所有版本比较

    partition函数是快排的核心部分 它的目的就是将数组划分为<=pivot和>pivot两部分,或者是<pivot和>=pivot 其实现方法大体有两种,单向扫描版本和双向 ...

  5. 算法模板——Dinic网络最大流 1

    实现功能:同sap网络最大流 今天第一次学Dinic,感觉最大的特点就是——相当的白话,相当的容易懂,而且丝毫不影响复杂度,顶多也就是代码长个几行 主要原理就是每次用spfa以O(n)的时间复杂度预处 ...

  6. Postgresql 经纬度

    增加一列: add column `location` geometry default p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px &q ...

  7. Robot Framework 关键字自定义

    关键字自定义有三种方法: 1.在自动化目录中右键-->New User Keyword 2.选中case中的步骤(可选多行),右键-->Extract Keyword 3.自己写模块,自定 ...

  8. 表达式计算 java 后缀表达式

    题目: 问题描述 输入一个只包含加减乖除和括号的合法表达式,求表达式的值.其中除表示整除. 输入格式 输入一行,包含一个表达式. 输出格式 输出这个表达式的值. 样例输入 1-2+3*(4-5) 样例 ...

  9. PHP语言开发微信公众平台(订阅号)之开启开发者模式

    (1)打开上一篇我们从花生壳官网获得的外网网址就会看到localhost根目录下的文件(这里不再赘述php环境的搭建).注:因为外网网址在能联网时,访问外网网址的任何人都能看到根目录下的所有文件,不仅 ...

  10. 用 Visual Studio Code 调试 Node.js

    环境: Visual Studio Code  Node.js 1. 关闭运行中的程序 2.打开入口文件,我这里的入口文件为 app.js 3.点击左侧菜单栏的 debug 按钮 4.点击运行按钮 5 ...