设计模式—— 十七:装饰器模式
@
什么是装饰器模式?
装饰器模式的定义:
Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.(动态地给一个对象添加一些额外的职责。 就增加功能来说,装饰模式相比生成子类更为灵活。) |
装饰模式的通用类图如图17-1所示。
在装饰器模式类图中包含如下几个角色:
● Component(抽象构件)
抽象构件它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法。
● ConcreteComponent (具体构件)
具体构件 ConcreteComponent是最核心、最原始、最基本的接口或抽象类的实现,要装饰的就是它。
● Decorator(抽象装饰类)
抽象装饰类也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
● ConcreteDecorator(具体装饰类)
具体装饰类是抽象装饰类的子类,负责向构件添加新的职责。每 一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
下面来看一下具体的代码实现:
- 抽象构件:
/**
* @author 三分恶
* @date 2020年7月10日
* @description 抽象构件
*/
public abstract class Component {
//抽象的方法
public abstract void operate();
}
- 具体构件:
/**
* @author 三分恶
* @date 2020年7月10日
* @description 具体构件
*/
public class ConcreteComponent extends Component{
//具体实现
@Override
public void operate() {
System.out.println("do Something");
}
}
- 抽象装饰器:
/**
* @author 三分恶
* @date 2020年7月10日
* @description 抽象装饰者
*/
public abstract class Decorator extends Component{
private Component component = null;
//通过构造函数传递被修饰者
public Decorator(Component _component) {
this.component = _component;
}
//委托给被装饰者执行
@Override
public void operate() {
this.component.operate();
}
}
- 具体装饰者:
public class ConcreteDecorator1 extends Decorator {
/**
* 定义被装饰者
*
* @param _component
*/
public ConcreteDecorator1(Component _component) {
super(_component);
}
// 定义自己的修饰方法
private void method1() {
System.out.println("method1 修饰");
}
//重写父类的operate方法
@Override
public void operate() {
this.method1();
super.operate();
}
}
public class ConcreteDecorator2 extends Decorator {
/**
* 定义被装饰者
*
* @param _component
*/
public ConcreteDecorator2(Component _component) {
super(_component);
}
// 定义自己的修饰方法
private void method2() {
System.out.println("method2 修饰");
}
//重写父类的operate方法
@Override
public void operate() {
this.method2();
super.operate();
}
}
- 场景类:通过Client类来模拟高层模块的耦合关系,看看装饰模式是如何运行的
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
Component component=new ConcreteComponent();
//第一次装饰
component=new ConcreteDecorator1(component);
//第二次装饰
component=new ConcreteDecorator2(component);
//执行operate方法
component.operate();
}
}
运行结果:
为什么要用装饰器模式
上面已经引入了装饰器的模式的定义,那么为什么要用装饰器模式呢?
有时我们希望给某个对象而不是整个类添加一些功能,例如:一个图形用户界面工具箱允许你对任意一个用户界面组件添加一些特性,例如边框。
使用继承机制是添加功能的一种有效途径,但这种方法不够灵活,因为边框的选择是静态的,用户不能控制对组件加边框的方式和时机。
下面是一个具体的业务场景实例。
有一个咖啡店,销售各种各样的咖啡,拿铁,卡布奇洛,蓝山咖啡等,在冲泡前,会询问顾客是否要加糖,加奶,加薄荷等。这样不同的咖啡配上不同的调料就会卖出不同的价格。
使用装饰器模式前
基于这个业务场景,首先设计一个抽象的咖啡类,然后每种咖啡不同的实现。
- 抽象咖啡类:
public abstract class Coffee {
// 是否加了牛奶
protected boolean addedMilk;
// 是否加了糖
protected boolean addedSugar;
// 是否加了薄荷
protected boolean addedMint;
/**
* 获取咖啡得名字
*/
public abstract String getName();
/**
* 获取咖啡的价格
*/
public abstract double getPrice();
}
- 实现之一:蓝山咖啡
public class BuleCoffee extends Coffee {
@Override
public String getName() {
StringBuilder name = new StringBuilder();
name.append("蓝山");
if (addedMilk) {
name.append("牛奶");
}
if (addedMilk) {
name.append("薄荷");
}
if (addedSugar) {
name.append("加糖");
}
return name.toString();
}
@Override
public double getPrice() {
double price = 10;
if (addedMilk) {
price += 1.1;
}
if (addedMilk) {
price += 3.2;
}
if (addedSugar) {
price += 2.7;
}
return price;
}
}
OK,业务已经实现,但是存在什么问题呢?
- 扩展麻烦:拿铁一种实现,加糖拿铁一种实现,加牛奶加糖又是一种实现
- 代码重复:存在一些重复的加糖、加牛奶的代码
- 系统庞大:咖啡里每加一种东西就要有一个新的实现,到最后实现类会非常多。
引入装饰器模式
所以,该考虑引入装饰器模式了。
设计出一个抽象装饰类,它也继承自coffee,具体的装饰类,继承抽象装饰类。
- 抽象咖啡装饰类:
public abstract class CoffeeDecorator extends Coffee {
private Coffee delegate;
public CoffeeDecorator(Coffee coffee) {
this.delegate = coffee;
}
@Override
public String getName() {
return delegate.getName();
}
@Override
public double getPrice() {
return delegate.getPrice();
}
}
- MilkCoffeeDecorator:
public class MilkCoffeeDecorator extends CoffeeDecorator {
public MilkCoffeeDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getName() {
return "牛奶, " + super.getName();
}
@Override
public double getPrice() {
return 1.1 + super.getPrice();
}
}
类似地实现出MintCoffeeDecorator,SugarCoffeeDecorator。
- Client类:
public class Client {
public static void main(String[] args) {
// 得到一杯原始的蓝山咖啡
Coffee blueCoffee = new BlueCoffee();
System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
// 加入牛奶
blueCoffee = new MilkCoffeeDecorator(blueCoffee);
System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
// 再加入薄荷
blueCoffee = new MintCoffeeDecorator(blueCoffee);
System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
// 再加入糖
blueCoffee = new SugarCoffeeDecorator(blueCoffee);
System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
}
}
装饰器模式优缺点
装饰器模式优点
● 装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,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。
参考:
【1】:《设计模式之禅》
【2】:《design patter java》
【3】:《研磨设计模式》
【4】:【一起学系列】之装饰器模式:不改代码增强功能?
【5】:《JAVA与模式》之装饰模式