认真学习设计模式之装饰者模式(Decorator Pattern)

简介: 写在前言利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。通过动态地组合对象,可以写新的代码添加新功能,而无须修改现有代码。既然没有改变现有代码,那么引进bug或产生意外副作用的机会将大幅度减少。

写在前言


利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。通过动态地组合对象,可以写新的代码添加新功能,而无须修改现有代码。既然没有改变现有代码,那么引进bug或产生意外副作用的机会将大幅度减少。


上面对应着一条重要的设计原则-“开闭原则”,类应该对扩展开发,对修改关闭。

那么有哪些设计模式遵从这一原则呢?其中一个就是“装饰者模式”

【1】装饰者模式

装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

几个特点

装饰者和被装饰者对象有相同的超类型;

你可以使用一个或多个装饰者包装一个对象;

既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)场合,可以用装饰过的对象代替它;

装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的;

对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。

组合与继承


继承是给一个类添加行为的比较有效的途径。通过使用继承,可以使得子类在拥有自身方法的同时,还可以拥有父类的方法。但是使用继承是静态的,在编译的时候就已经决定了子类的行为,我们不便于控制增加行为的方式和时机。


组合即将一个对象嵌入到另一个对象中,由另一个对象来决定是否引用该对象来扩展自己的行为。这是一种动态的方式,我们可以在应用程序中动态的控制。


与继承相比,组合关系的优势就在于不会破坏类的封装性,且具有较好的松耦合性,可以使系统更加容易维护。但是它的缺点就在于要创建比继承更多的对象。

UML类图

装饰者模式角色:


Component:定义一个对象接口,可以给这些对象动态添加职责。真实对象和装饰者对象有相同的接口,这样客户端不用知道内部有装饰者对象(Decorator)存在的,还是以之前处理真实对象的相同方式来和装饰者对象交互。


ConcreteComponent:是定义了一个具体的对象(例如:人),也可以给这个对象添加一些其他职责。


Decorator:装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对Component来说,是无需知道Decorator存在的。


ConcreteDecorator:就是具体的装饰对象了(衣服,鞋子…),它起到了给Component添加职责的功能。

【2】代码实现

① 定义一个Component对象接口(MyCar),汽车在跑

public interface MyCar {
   void run(); //汽车行驶
}


② 定义一个具体真实的对象ConcreteComponent

这里是Car,就是具体的汽车,未装饰的汽车。

public class Car implements MyCar{
   @Override
   public void run() {
       System.out.println("汽车在跑");
   }
}

③ 装饰抽象类Decorator

SuperCar这里需要持有一个真实对象的引用,也就是Car对象

public class SuperCar implements MyCar {
   protected MyCar car;//持有一个真实对象的引用
   @Override
   public void run() {
       car.run();//这里调用真实对象的移动方法
   }
   //构造的时候传参
   public SuperCar(MyCar car) {
       super();
       this.car = car;
   }
}

④ 开始实现具体的装饰对象ConcreteDecorator

public class FlayCar extends SuperCar {
   public FlayCar(MyCar car) {
       super(car);
   }
   //这里就是新增的功能
   public void flay(){
       System.out.println("---天上飞");
   }
   @Override
   public void run() {
       super.run();
       flay();//在原有移动的基础上,装饰了一个fly的功能
   }
}
public class WaterCar extends SuperCar {
   public WaterCar(MyCar car) {
       super(car);
   }
   //这里就是新增的功能
   public void swim(){
       System.out.println("---水里游");
   }
   @Override
   public void run() {
       super.run();
       swim();//在原有移动的基础上,装饰了一个swim的功能
   }
}

⑤ 测试代码

public class CustomerTest {
   public static void main(String[] args) {
       Car car = new Car();
       car.run();//这里打印未增加新功能的时候:汽车移动
       System.out.println("--------增加飞行功能-------");
       FlayCar flyCar = new FlayCar(car);//将真实对象传入装饰对象中
       flyCar.run();//这里就是增加了飞行后的装饰
       System.out.println("--------增加潜水功能-------");
       WaterCar waterCar = new WaterCar(car);//将真实对象传入装饰对象中
       waterCar.run();//这里就是增加了潜水功能后的装饰
   }
}

代码的uml类图结构如下:


【3】真实世界的装饰者:Java I/O


Java中I/O的相关类中许多类都是装饰者。下面是一个典型的对象集合,用装饰者来将功能结合起来,以读取文件数据:

BufferedInputStream及LineNumberInputStream都扩展自FilterInputStream,而FilterInputStream是一个抽象的装饰类。


FilterInputStream类的主要属性与构造方法如下:

public class FilterInputStream extends InputStream {
    protected volatile InputStream in;
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
    //...
}    

装饰j a v a . i o类

你会发现“输出”流的设计方式也是一样的。你可能还会发现Reader/Writer流(作为基于字符数据的输入输出)和输入流/输出流的类相当类似(虽然有一些小差异和不一致之处,但是相当雷同,所以你应该可以了解这些类)。


但是JavaAI/O也引出装饰者模式的一个“缺点”:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会造成使用此API程序员的困扰。



编写自己的J a v a I / 0装饰者


编写一个装饰者,把输入流内的所有大写字符转成小写。举例:当读取“I k n o w t h e D e c o r a t o r P a t t e r n t h e r e f o r e I R U L E !”,装饰者会将它转成“i k n o w t h e d e c o r a t o r p a t t e r n t h e r e f o r e i r u l e !”


public class LowerCaseInputStream extends FilterInputStream {
  public LowerCaseInputStream(InputStream in) {
    super(in);
  }
  /**
  现在,必须实现两个read()方法,一个针对字节,一个针对字节数组,把每个是大写字符
  的字节(每个代表一个字符)转成小写。
  */
  public int read() throws IOException {
    int c = super.read();
    return (c == -1 ? c : Character.toLowerCase((char)c));
  }
  public int read(byte[] b, int offset, int len) throws IOException {
    int result = super.read(b, offset, len);
    for (int i = offset; i < offset+result; i++) {
    b[i] = (byte)Character.toLowerCase((char)b[i]);
  }
  return result;
  }
}

测试如下:

public class InputTest {
  public static void main(String[] args) throws IOException {
    int c;
    try {
      InputStream in =new LowerCaseInputStream(
        new BufferedInputStream(
          new FileInputStream("test.txt")));
      while((c = in.read()) >= 0) {
        System.out.print((char)c);
      }
      in.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

运行结果如下:


【4】总结


装饰模式(Decorator)也叫包装器模式(Wrapper)。装饰模式降低系统的耦合度,可以动态的增加或删除对象的职责,并使得需要装饰的具体构建类和具体装饰类可以独立变化以便增加新的具体构建类和具体装饰类。


优点:

  • 扩展功能强,相比继承来说更灵活。继承的话会导致子类个数增加。而装饰者模式不会出现这种情况。
  • 可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更加强大的对象。
  • 具体构建类和具体装饰类可以独立变化,用户可以根据需要自己增加新的构件子类和具体装饰类。


不足:

  • 产生很多小对象,大量小对象会占据内存。一定程度上影响了性能。
  • 如果过度使用,会让程序变得很复杂。
  • 装饰模式易于出错,调试排查比较麻烦。



开发中应用的场景:

IO中输入流和输出流


Swing包中图形界面构件功能


Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,增强了request对象的功能。


Struts2中,request,response,session对象的处理。


装饰模式和桥接模式的区别:

两个模式都是为了解决过多子类对象的问题,桥接模式是对象自身有过多的维度,造成过多的子类。而让维度分类后在搭建一个桥梁来联系起来。而装饰模式是解决在增加新功能的时候产生多个类的问题。




目录
相关文章
|
11天前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
|
25天前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
39 0
|
1月前
|
设计模式 Java Kotlin
Kotlin 学习笔记- 改良设计模式 - 装饰者模式
Kotlin 学习笔记- 改良设计模式 - 装饰者模式
26 0
|
2月前
|
设计模式
设计模式-工厂模式 Factory Pattern(简单工厂、工厂方法、抽象工厂)
这篇文章详细解释了工厂模式,包括简单工厂、工厂方法和抽象工厂三种类型。每种模式都通过代码示例展示了其应用场景和实现方法,并比较了它们之间的差异。简单工厂模式通过一个工厂类来创建各种产品;工厂方法模式通过定义一个创建对象的接口,由子类决定实例化哪个类;抽象工厂模式提供一个创建相关或依赖对象家族的接口,而不需要明确指定具体类。
设计模式-工厂模式 Factory Pattern(简单工厂、工厂方法、抽象工厂)
|
1月前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
22 0
|
2月前
|
设计模式 Java
设计模式--适配器模式 Adapter Pattern
这篇文章介绍了适配器模式,包括其基本介绍、工作原理以及类适配器模式、对象适配器模式和接口适配器模式三种实现方式。
|
5月前
|
设计模式 存储 算法
设计模式学习心得之五种创建者模式(2)
设计模式学习心得之五种创建者模式(2)
45 2
|
5月前
|
设计模式
设计模式-05建造者模式(Builder Pattern)
设计模式-05建造者模式(Builder Pattern)
|
5月前
|
设计模式 Java API
程序技术好文:设计模式:装饰者模式
程序技术好文:设计模式:装饰者模式
25 0
|
16天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式

热门文章

最新文章

  • 1
    C++一分钟之-设计模式:工厂模式与抽象工厂
    43
  • 2
    《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
    46
  • 3
    C++一分钟之-C++中的设计模式:单例模式
    54
  • 4
    《手把手教你》系列基础篇(九十三)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-上篇(详解教程)
    38
  • 5
    《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
    62
  • 6
    Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
    57
  • 7
    Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
    41
  • 8
    Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
    50
  • 9
    Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
    106
  • 10
    Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
    78