什么是装饰器模式?
装饰器模式的定义:
Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.(动态地给一个对象添加一些额外的职责。 就增加功能来说,装饰模式相比生成子类更为灵活。) |
装饰模式的通用类图如图17-1所示。
图17-1:装饰器模式通用类图
在装饰器模式类图中包含如下几个角色:
● Component(抽象构件)
抽象构件它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法。
● ConcreteComponent (具体构件)
具体构件 ConcreteComponent是最核心、最原始、最基本的接口或抽象类的实现,要装饰的就是它。
● Decorator(抽象装饰类)
抽象装饰类也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
● ConcreteDecorator(具体装饰类)
具体装饰类是抽象装饰类的子类,负责向构件添加新的职责。每 一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
下面来看一下具体的代码实现:
- 抽象构件:
<span style="color:#141418"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#a0a1a7"><em>/**
* <span style="color:#a626a4">@author</span> 三分恶
* <span style="color:#a626a4">@date</span> 2020年7月10日
* <span style="color:#a626a4">@description</span> 抽象构件
*/</em></span>
<span style="color:#a626a4">public</span> <span style="color:#a626a4">abstract</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Component</span> {
<span style="color:#a0a1a7"><em>//抽象的方法 </em></span>
<span style="color:#a626a4">public</span> <span style="color:#a626a4">abstract</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">operate</span>();
}
</code></span></span>
- 具体构件:
<span style="color:#141418"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#a0a1a7"><em>/**
* <span style="color:#a626a4">@author</span> 三分恶
* <span style="color:#a626a4">@date</span> 2020年7月10日
* <span style="color:#a626a4">@description</span> 具体构件
*/</em></span>
<span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">ConcreteComponent</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">Component</span>{
<span style="color:#a0a1a7"><em>//具体实现</em></span>
<span style="color:#4078f2">@Override</span>
<span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">operate</span>() {
System.out.println(<span style="color:#50a14f">"do Something"</span>);
}
}
</code></span></span>
- 抽象装饰器:
<span style="color:#141418"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#a0a1a7"><em>/**
* <span style="color:#a626a4">@author</span> 三分恶
* <span style="color:#a626a4">@date</span> 2020年7月10日
* <span style="color:#a626a4">@description</span> 抽象装饰者
*/</em></span>
<span style="color:#a626a4">public</span> <span style="color:#a626a4">abstract</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Decorator</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">Component</span>{
<span style="color:#a626a4">private</span> <span style="color:#986801">Component</span> <span style="color:#986801">component</span> <span style="color:#ab5656">=</span> <span style="color:#0184bb">null</span>;
<span style="color:#a0a1a7"><em>//通过构造函数传递被修饰者 </em></span>
<span style="color:#a626a4">public</span> <span style="color:#4078f2">Decorator</span>(Component _component) {
<span style="color:#c18401">this</span>.component = _component;
}
<span style="color:#a0a1a7"><em>//委托给被装饰者执行</em></span>
<span style="color:#4078f2">@Override</span>
<span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">operate</span>() {
<span style="color:#c18401">this</span>.component.operate();
}
}
</code></span></span>
- 具体装饰者:
<span style="color:#141418"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">ConcreteDecorator1</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">Decorator</span> {
<span style="color:#a0a1a7"><em>/**
* 定义被装饰者
*
* <span style="color:#a626a4">@param</span> _component
*/</em></span>
<span style="color:#a626a4">public</span> <span style="color:#4078f2">ConcreteDecorator1</span>(Component _component) {
<span style="color:#c18401">super</span>(_component);
}
<span style="color:#a0a1a7"><em>// 定义自己的修饰方法</em></span>
<span style="color:#a626a4">private</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">method1</span>() {
System.out.println(<span style="color:#50a14f">"method1 修饰"</span>);
}
<span style="color:#a0a1a7"><em>//重写父类的operate方法</em></span>
<span style="color:#4078f2">@Override</span>
<span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">operate</span>() {
<span style="color:#c18401">this</span>.method1();
<span style="color:#c18401">super</span>.operate();
}
}
<span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">ConcreteDecorator2</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">Decorator</span> {
<span style="color:#a0a1a7"><em>/**
* 定义被装饰者
*
* <span style="color:#a626a4">@param</span> _component
*/</em></span>
<span style="color:#a626a4">public</span> <span style="color:#4078f2">ConcreteDecorator2</span>(Component _component) {
<span style="color:#c18401">super</span>(_component);
}
<span style="color:#a0a1a7"><em>// 定义自己的修饰方法</em></span>
<span style="color:#a626a4">private</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">method2</span>() {
System.out.println(<span style="color:#50a14f">"method2 修饰"</span>);
}
<span style="color:#a0a1a7"><em>//重写父类的operate方法</em></span>
<span style="color:#4078f2">@Override</span>
<span style="color:#a626a4">public</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">operate</span>() {
<span style="color:#c18401">this</span>.method2();
<span style="color:#c18401">super</span>.operate();
}
}
</code></span></span>
- 场景类:通过Client类来模拟高层模块的耦合关系,看看装饰模式是如何运行的
<span style="color:#141418"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Client</span> {
<span style="color:#a0a1a7"><em>/**
* <span style="color:#a626a4">@param</span> args
*/</em></span>
<span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">main</span>(String[] args) {
Component component=<span style="color:#a626a4">new</span> <span style="color:#4078f2">ConcreteComponent</span>();
<span style="color:#a0a1a7"><em>//第一次装饰</em></span>
component=<span style="color:#a626a4">new</span> <span style="color:#4078f2">ConcreteDecorator1</span>(component);
<span style="color:#a0a1a7"><em>//第二次装饰</em></span>
component=<span style="color:#a626a4">new</span> <span style="color:#4078f2">ConcreteDecorator2</span>(component);
<span style="color:#a0a1a7"><em>//执行operate方法</em></span>
component.operate();
}
}
</code></span></span>
运行结果:
为什么要用装饰器模式
上面已经引入了装饰器的模式的定义,那么为什么要用装饰器模式呢?
有时我们希望给某个对象而不是整个类添加一些功能,例如:一个图形用户界面工具箱允许你对任意一个用户界面组件添加一些特性,例如边框。
使用继承机制是添加功能的一种有效途径,但这种方法不够灵活,因为边框的选择是静态的,用户不能控制对组件加边框的方式和时机。
下面是一个具体的业务场景实例。
有一个咖啡店,销售各种各样的咖啡,拿铁,卡布奇洛,蓝山咖啡等,在冲泡前,会询问顾客是否要加糖,加奶,加薄荷等。这样不同的咖啡配上不同的调料就会卖出不同的价格。
使用装饰器模式前
基于这个业务场景,首先设计一个抽象的咖啡类,然后每种咖啡不同的实现。
- 抽象咖啡类:
<span style="color:#141418"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">abstract</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Coffee</span> {
<span style="color:#a0a1a7"><em>// 是否加了牛奶</em></span>
<span style="color:#a626a4">protected</span> <span style="color:#986801">boolean</span> addedMilk;
<span style="color:#a0a1a7"><em>// 是否加了糖</em></span>
<span style="color:#a626a4">protected</span> <span style="color:#986801">boolean</span> addedSugar;
<span style="color:#a0a1a7"><em>// 是否加了薄荷</em></span>
<span style="color:#a626a4">protected</span> <span style="color:#986801">boolean</span> addedMint;
<span style="color:#a0a1a7"><em>/**
* 获取咖啡得名字
*/</em></span>
<span style="color:#a626a4">public</span> <span style="color:#a626a4">abstract</span> String <span style="color:#4078f2">getName</span>();
<span style="color:#a0a1a7"><em>/**
* 获取咖啡的价格
*/</em></span>
<span style="color:#a626a4">public</span> <span style="color:#a626a4">abstract</span> <span style="color:#986801">double</span> <span style="color:#4078f2">getPrice</span>();
}
</code></span></span>
- 实现之一:蓝山咖啡
<span style="color:#141418"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">BuleCoffee</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">Coffee</span> {
<span style="color:#4078f2">@Override</span>
<span style="color:#a626a4">public</span> String <span style="color:#4078f2">getName</span>() {
<span style="color:#986801">StringBuilder</span> <span style="color:#986801">name</span> <span style="color:#ab5656">=</span> <span style="color:#a626a4">new</span> <span style="color:#4078f2">StringBuilder</span>();
name.append(<span style="color:#50a14f">"蓝山"</span>);
<span style="color:#a626a4">if</span> (addedMilk) {
name.append(<span style="color:#50a14f">"牛奶"</span>);
}
<span style="color:#a626a4">if</span> (addedMilk) {
name.append(<span style="color:#50a14f">"薄荷"</span>);
}
<span style="color:#a626a4">if</span> (addedSugar) {
name.append(<span style="color:#50a14f">"加糖"</span>);
}
<span style="color:#a626a4">return</span> name.toString();
}
<span style="color:#4078f2">@Override</span>
<span style="color:#a626a4">public</span> <span style="color:#986801">double</span> <span style="color:#4078f2">getPrice</span>() {
<span style="color:#986801">double</span> <span style="color:#986801">price</span> <span style="color:#ab5656">=</span> <span style="color:#986801">10</span>;
<span style="color:#a626a4">if</span> (addedMilk) {
price += <span style="color:#986801">1.1</span>;
}
<span style="color:#a626a4">if</span> (addedMilk) {
price += <span style="color:#986801">3.2</span>;
}
<span style="color:#a626a4">if</span> (addedSugar) {
price += <span style="color:#986801">2.7</span>;
}
<span style="color:#a626a4">return</span> price;
}
}
</code></span></span>
OK,业务已经实现,但是存在什么问题呢?
- 扩展麻烦:拿铁一种实现,加糖拿铁一种实现,加牛奶加糖又是一种实现
- 代码重复:存在一些重复的加糖、加牛奶的代码
- 系统庞大:咖啡里每加一种东西就要有一个新的实现,到最后实现类会非常多。
引入装饰器模式
所以,该考虑引入装饰器模式了。
设计出一个抽象装饰类,它也继承自coffee,具体的装饰类,继承抽象装饰类。
- 抽象咖啡装饰类:
<span style="color:#141418"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">abstract</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">CoffeeDecorator</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">Coffee</span> {
<span style="color:#a626a4">private</span> Coffee delegate;
<span style="color:#a626a4">public</span> <span style="color:#4078f2">CoffeeDecorator</span>(Coffee coffee) {
<span style="color:#c18401">this</span>.delegate = coffee;
}
<span style="color:#4078f2">@Override</span>
<span style="color:#a626a4">public</span> String <span style="color:#4078f2">getName</span>() {
<span style="color:#a626a4">return</span> delegate.getName();
}
<span style="color:#4078f2">@Override</span>
<span style="color:#a626a4">public</span> <span style="color:#986801">double</span> <span style="color:#4078f2">getPrice</span>() {
<span style="color:#a626a4">return</span> delegate.getPrice();
}
}
</code></span></span>
- MilkCoffeeDecorator:
<span style="color:#141418"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">MilkCoffeeDecorator</span> <span style="color:#a626a4">extends</span> <span style="color:#4078f2">CoffeeDecorator</span> {
<span style="color:#a626a4">public</span> <span style="color:#4078f2">MilkCoffeeDecorator</span>(Coffee coffee) {
<span style="color:#c18401">super</span>(coffee);
}
<span style="color:#4078f2">@Override</span>
<span style="color:#a626a4">public</span> String <span style="color:#4078f2">getName</span>() {
<span style="color:#a626a4">return</span> <span style="color:#50a14f">"牛奶, "</span> + <span style="color:#c18401">super</span>.getName();
}
<span style="color:#4078f2">@Override</span>
<span style="color:#a626a4">public</span> <span style="color:#986801">double</span> <span style="color:#4078f2">getPrice</span>() {
<span style="color:#a626a4">return</span> <span style="color:#986801">1.1</span> + <span style="color:#c18401">super</span>.getPrice();
}
}
</code></span></span>
类似地实现出MintCoffeeDecorator,SugarCoffeeDecorator。
- Client类:
<span style="color:#141418"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#a626a4">public</span> <span style="color:#a626a4">class</span> <span style="color:#4078f2">Client</span> {
<span style="color:#a626a4">public</span> <span style="color:#a626a4">static</span> <span style="color:#a626a4">void</span> <span style="color:#4078f2">main</span>(String[] args) {
<span style="color:#a0a1a7"><em>// 得到一杯原始的蓝山咖啡</em></span>
<span style="color:#986801">Coffee</span> <span style="color:#986801">blueCoffee</span> <span style="color:#ab5656">=</span> <span style="color:#a626a4">new</span> <span style="color:#4078f2">BlueCoffee</span>();
System.out.println(blueCoffee.getName() + <span style="color:#50a14f">": "</span> + blueCoffee.getPrice());
<span style="color:#a0a1a7"><em>// 加入牛奶</em></span>
blueCoffee = <span style="color:#a626a4">new</span> <span style="color:#4078f2">MilkCoffeeDecorator</span>(blueCoffee);
System.out.println(blueCoffee.getName() + <span style="color:#50a14f">": "</span> + blueCoffee.getPrice());
<span style="color:#a0a1a7"><em>// 再加入薄荷</em></span>
blueCoffee = <span style="color:#a626a4">new</span> <span style="color:#4078f2">MintCoffeeDecorator</span>(blueCoffee);
System.out.println(blueCoffee.getName() + <span style="color:#50a14f">": "</span> + blueCoffee.getPrice());
<span style="color:#a0a1a7"><em>// 再加入糖</em></span>
blueCoffee = <span style="color:#a626a4">new</span> <span style="color:#4078f2">SugarCoffeeDecorator</span>(blueCoffee);
System.out.println(blueCoffee.getName() + <span style="color:#50a14f">": "</span> + blueCoffee.getPrice());
}
}
</code></span></span>
装饰器模式优缺点
装饰器模式优点
● 装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,Component类无须知 道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具 体的构件。
● 装饰模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返 回的对象还是Component,实现的还是is-a的关系。
● 装饰模式可以动态地扩展一个实现类的功能,这不需要多说,装饰模式的定义就是如此。
装饰器模式缺点
● 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接 的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多 的系统资源,在一定程序上影响程序的性能。
● 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出 错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。
装饰器模式应用场景
● 需要扩展一个类的功能,或给一个类增加附加功能。
● 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
● 需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式。
扩展-Java中的装饰器模式
在Java中比较典型的应用就是I/O流。
以下是Java I/O流InputStream的部分类图:
通过图中可以看出:
● 抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。
● 具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。
● 抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。
● 具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。