买了新房(毛胚房)需要装修,对新房进行装修并没有改变房子用于居住的本质,但它让房子变的更漂亮,更加满足居家的需求。在软件设计中,我们也可以用类似的技术对原有对象(新房)的功能进行扩展(装修),以获得更加符合用户需求的对象。这种技术在设计模式中被成为装饰模式,装饰模式可以动态地给父类增加功能。
装饰模式可以在不改变一个对象本身的基础上上给对象增加额外的新行为,在现实生活中,这种情况比比皆是,如一张照片,可以不改变照片本身,给它增加一个相框,使得它具有防潮的功能,而且用户可以根据需要给它增加不同类型的相框,甚至可以在一个小的相框的外面再套一个大相框。
在软件外发中,类似给照片加相框的情况也随处可见。如可以给一个图形界面构件增加边框、滚动等新的特性,给一个数据加密类增加更复杂的加密算法等。
一般有两种方式可以实现给一个类或对象增加行为:
1.继承机制
使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
2.关联机制
关联机制是更加灵活的方法,即将一个类的对象嵌入另一个新对象中,由另一个对象类来决定是否调用嵌入对象的行为并扩展新的行为,我们称这个新的对象(即另一个对象)为装饰器(Decorator)。为了使得装饰器与它所装饰的对象对客户端来说透明,装饰器类和被装饰的类必须实现相同的接口,客户端使用时无须关心一个类的对象是否被装饰过,可以一致性地使用未被装饰的对象以及装饰好的对象。我们可以在被装饰的类中调用在装饰器类中定义的方法,实现更多复杂的功能,而且由于装饰器类和被装饰的类实现了相同的接口,已经被装饰过的对象可以继续作为新的被装饰对象进行装饰,这种透明性使得我们可以递归嵌套多个装饰,从而可以添加任意多的功能。
装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的动机。
装饰模式定义(Decorator Pattern)定义:动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以成为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。
英文定义:“Attach additional responsibilities to an object dynamically.Decorators provide a flexible alternative to subclassing for extending functionality.”
图1 装饰模式结构图
装饰模式包含如下角色。
1.Component(抽象构件)
抽象构件定义了对象的接口,可以给这些对象动态增加职责(方法),抽象构件是具体构件和抽象装饰类的共同父类,它声明了在具体构件中实现的业务方法,它的引入可以是客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
2.ConcreteComponent(具体构件)
具体构件定义了具体的构件对象,实现了在抽象构件中声明的方法,装饰器模式可以给它增加额外的职责(方法)。
3.Decorator(抽象装饰类)
抽象装饰类是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的饮用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
4.ConcreteDecorator(具体装饰类)
具体装饰类是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰器都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法以便扩充对象的行为。
题目:某图书管理系统中,书籍类(Book)具有借书方法borrowBook()和还书方法returnBook() 。现需要动态给书籍对象添加冻结方法freeze()和遗失方法lose()。使用装饰模式设计该系统,绘制类图并编程实现。
1.抽象构件类ABookt(抽象书本)
public interface ABook {
public void returnBook();
public void borrowBook();
}
ABook是抽象构件类,在其中声明了returnBook()和 borrowBook()方法,无论书本如何改变,这两个方法都必须具有,它是具体构件和装饰器共有的方法。
2.具体构件类Book(书本类)
public final class Book implements ABook{
public Book(){
System.out.println("这是一本书!");
}
@Override
public void returnBook() {
System.out.println("图书已归还!!");
}
@Override
public void borrowBook() {
System.out.println("图书已借阅!");
}
}
Book是ABook的子类,它是具体构件类,提供了returnBook()和borrowBook()方法的实现,它是一个可以被装饰的类。在这里Book被声明为final类型,意味着不能通过继承来扩展其功能,但是可以通过关联关系来扩展,也就是通过使用装饰器来装饰它。
3.抽象装饰类Changer(变化类)
public class Changer implements ABook{
private ABook book;
public Changer(ABook book){
this.book = book;
}
@Override
public void returnBook() {
book.returnBook();
}
@Override
public void borrowBook() {
book.borrowBook();
}
}
Changer是抽象装饰类,它是所有具体装饰类的父类,同时它也是抽象构件的子类。Changer类是装饰类的核心,它定义了一个抽象构件类型的对象book,可以通过构造函数或者Setter方法来给该对象赋值,在本实例中使用的是构造函数,并且它也实现了returnBook()和borrowBook()方法,但是它通过调用book对象中的returnBook()和borrowBook()来实现,这样就可以保证原有方法不会丢失,而且可以在它的子类中增加新的方法,扩展原有对象的功能。
4.具体装饰类freezeBook(冻结书类)
public class freezeBook extends Changer{
public freezeBook(ABook book){
super(book);
}
public void freeze(){
System.out.println("图书已冻结!!!");
}
}
freezeBook类是Changer的子类,它继承了在Changer中定义的方法,还增加新的方法,也就是说它即可以调用原有对象的方法,又可以对其进行补充,为其增加新的职责,如可以冻结图书。
5.具体装饰类loseBook(丢失书类)
public class loseBook extends Changer{
public loseBook(ABook book){
super(book);
}
public void lose(){
System.out.println("图书已丢失!!!!!");
}
}
loseBook类也是Changer的子类,它继承类在Changer中定义的方法,还增加了新的方法lose(),实现图书丢失。
客户端测试类Client如下:
public class Client {
public static void main(String[] args) {
ABook book;
book = new Book();
book.borrowBook();
book.returnBook();
System.out.println("--------------");
freezeBook b1 = new freezeBook(book);
b1.borrowBook();
b1.returnBook();
b1.freeze();
System.out.println("--------------");
loseBook b2 = new loseBook(book);
b2.borrowBook();
b2.returnBook();
b2.lose();
}
}
3.4结果
(1)装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
(2)可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
(3)通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
(4)具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
(1)使用装饰器模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
(2)这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承模式更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
4.3模式适用环境
在以下情况可以使用装饰模式。