情境
要求写一个可以给人搭配不同服饰的系统,比如类似QQ、网络游戏或论坛都有的Avatar系统。
常规做法
UML类图
代码如下
Person类
public class Person {
public Person() {
}
private String name;
public Person(String name) {
this.name = name;
}
public void show() {
print("装扮的" + name);
}
}
服饰抽象类
public abstract class Finery {
public abstract void show();
}
各种服饰子类
public class TShirts extends Finery {
@Override
public void show() {
print("大T恤");
}
}
public class BigTrouser extends Finery {
@Override
public void show() {
print("垮裤");
}
}
public class Sneakers extends Finery {
@Override
public void show() {
print("破球鞋");
}
}
public class Suit extends Finery {
@Override
public void show() {
print("西装");
}
}
public class Tie extends Finery {
@Override
public void show() {
print("领带");
}
}
public class LeatherShoes extends Finery {
@Override
public void show() {
print("皮鞋");
}
}
测试代码
public class Test {
public static void main(String[] args) {
Person a = new Person("小A");
print("第一种装扮");
Finery tShirts = new TShirts();
Finery bigTrouser = new BigTrouser();
Finery sneakers = new Sneakers();
tShirts.show();
bigTrouser.show();
sneakers.show();
a.show();
print("第二种装扮");
Finery suit = new Suit();
Finery tie = new Tie();
Finery leatherShoes = new LeatherShoes();
suit.show();
tie.show();
leatherShoes.show();
a.show();
}
}
运行结果
tShirts.show();
bigTrouser.show();
sneakers.show();
a.show();
这样实现的问题是把‘大T恤’、‘垮裤’、‘破球鞋’一个词一个词的显示出来。这就好比:人光着身子,当着大家的面,先传T恤,再穿裤子,再穿鞋。应该在内部组装完毕,再显示出来。穿衣服还将就先后顺序,我们需要把所需的功能按正确的顺序串联起来进行控制。可以通过装饰模式来实现。
装饰模式
装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。[DP]
无论衣服、鞋子、领带其实都可以理解为对人的装饰。
装饰模式(Decorator)结构图
代码实现
Component类
public interface Component {
void operation();
}
ConcreteComponent类
public class ConcreteComponent implements Component {
@Override
public void operation() {
print("具体对象的操作");
}
}
Decorator类
public abstract class Decorator implements Component {
protected Component component;
/**
* 重写operation()
* 实际执行的是Component的operation()
*/
@Override
public void operation() {
if (component != null) {
component.operation();
}
}
public Component getComponent() {
return component;
}
public void setComponent(Component component) {
this.component = component;
}
}
ConcreteDecoratorA类
public class ConcreteDecoratorA extends Decorator {
/**
* 本类的独有功能,以区别于ConcreteDecoratorB
*/
private String addedState;
/**
* 运行原Component的operation()
* 再执行本类功能,相当于对原Component进行了装饰
*/
@Override
public void operation(){
super.operation();
addedState = "New State";
print("具体装饰对象A的操作");
}
}
ConcreteDecoratorB类
public class ConcreteDecoratorB extends Decorator {
/**
* 本类独有方法,以区别于ConcreteDecoratorA
*/
private void addedBehavior() {
}
@Override
public void operation() {
super.operation();
addedBehavior();
print("具体装饰对象B的操作");
}
}
测试代码
public class Test {
public static void main(String[] args){
ConcreteComponent c = new ConcreteComponent();
ConcreteDecoratorA d1 = new ConcreteDecoratorA();
ConcreteDecoratorB d2 = new ConcreteDecoratorB();
/**
* 装饰的方式是:
* 首先用ConcreteComponent实例化对象c
* 然后用ConcreteDecoratorA的实例化对象d1来包装c
* 再用ConcreteDecoratorB的对象d2包装d1
* 最终执行d2的operation()
*/
d1.setComponent(c);
d2.setComponent(d1);
d2.operation();
}
}
装饰模式是利用SetComponent来对对象进行包装的。这样每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中[DPE]。用刚才的例子来说就是,完全可以先穿外裤,再穿内裤,而不一定要先内后外。
学习模式要善于变通,如果只有一个ConcreteComponent类而没有抽象的Component类,那么Decorator类可以是ConcreteComponent的一个子类。同样的道理,如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。
根据装饰模式,修改之前换装的代码。
代码结构图
代码如下
Person类(ConcreteComponent)
public class Person {
public Person() {
}
private String name;
public Person(String name) {
this.name = name;
}
public void show() {
print("装扮的" + name);
}
}
服饰类(Decorator)
public class Finery extends Person {
protected Person component;
public void decorate(Person component) {
this.component = component;
}
@Override
public void show() {
if (component != null) {
component.show();
}
}
}
具体服饰类(ConcreteDecorator)
public class TShirts extends Finery {
@Override
public void show(){
print("大T恤");
super.show();
}
}
//其他类似,省略
测试代码
public class Test {
public static void main(String[] args) {
Person a = new Person("小A");
print("第一种装扮");
Sneakers sneakers = new Sneakers();
BigTrouser bigTrouser = new BigTrouser();
TShirts tShirts = new TShirts();
sneakers.decorate(a);
bigTrouser.decorate(sneakers);
tShirts.decorate(bigTrouser);
tShirts.show();
print("第二种装扮");
LeatherShoes leatherShoes = new LeatherShoes();
Suit suit = new Suit();
Tie tie = new Tie();
leatherShoes.decorate(a);
suit.decorate(leatherShoes);
tie.decorate(suit);
tie.show();
}
}
总结
装饰模式是为已用功能动态地添加更多功能的一种方式,新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。装饰模式提供了一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以再运行时根据需要有选择地、按顺序地使用装饰功能包装对象了[DP]。
装饰模式的优点是把类中的装饰功能从类中移除,这样可以简化原有的类。有效地把类的核心职责和装饰功能区分开。而且可以去除相关类中重复的装饰逻辑。
装饰模式的装饰顺序很重要,比如加密数据和过滤词汇都可以是数据持久化前的装饰功能,但若先加密了数据再过滤功能就会出问题了,最理想的情况,是保证装饰类之间彼此独立,这样它们就可以以任意的顺序进行组合了。