装饰模式

《大话设计模式》第6章内容,介绍装饰模式

Posted by GrayWind on October 18, 2018

装饰模式(Decorator)

动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。它的机构和具体代码如下

decorator

public interface Component {
	public void operation();
}

public class ConcreteComponent implements Component {

	@Override
	public void operation() {
		System.out.println("具体对象的操作");
	}

}

public class Decorator implements Component {
	protected Component component;
	
	public void setComponent(Component component) {
		this.component = component;
	}
	
	@Override
	public void operation() {
		if(component != null) {
			component.operation();
		}
	}

}

public class ConcreteDecoratorA extends Decorator {
	private String addState;//本类独有功能,以区别于ConcreteDecoratorB
	
	@Override
	/**
	 * 首先运行原Component的operation(),再执行本类的功能,如addedState,
	 * 相当于对原Component进行了修饰
	 */
	public void operation() {
		super.operation();
		addState = "New State";
		System.out.println("具体装饰对象A的操作");
	}
}

public class ConcreteDecoratorB extends Decorator {
	
	@Override
	/**
	 * 首先运行原Component的operation(),再执行本类的功能,如addedBehavior(),
	 * 相当于对原Component进行了修饰
	 */
	public void operation() {
		super.operation();
		addedBehavior();
		System.out.println("具体装饰对象B的操作");
	}
	
	private void addedBehavior() {
		System.out.println("具体装饰对象B的私有操作");
	}
}

测试运行和运行结果为

public class DecoratorTest {
	public static void main(String[] args) {
		ConcreteComponent c = new ConcreteComponent();
		ConcreteDecoratorA d1 = new ConcreteDecoratorA();
		ConcreteDecoratorB d2 = new ConcreteDecoratorB();
		
		d1.setComponent(c);
		d2.setComponent(d1);
		d2.operation();
	}
}

//具体对象的操作
//具体装饰对象A的操作
//具体装饰对象B的私有操作
//具体装饰对象B的操作

运行的输出结果很好理解,d2的component是d1,而d1的component是c。当d2.operation()的时候,先调用父类也就是Decorator.operation(),此时d2的component是ConcreteDecoratorA实例d1,检查ConcreteDecoratorA的方法,先调用Decorator.operation(),而d1的component是ConcreteComponent实例c,所以先输出”具体对象的操作” 。然后d1输出”具体装饰对象A的操作”,再返回到到d2部分,调用addedBehavior()输出”具体装饰对象B的私有操作”,最后是输出”具体装饰对象B的操作”。

装饰模式是利用SetComponent来对对象进行包装。这样每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关系如何被添加到对象链当中。

应用场景

以类似QQ秀那种常见的换装游戏为例,一个人要决定穿着,他有穿西装、穿拖鞋、穿T恤、穿皮鞋等等选择,我们不能简单地把它们全部写到Person类里面,因为如果我们要增加一个穿背心,就必须要修改Person类,这违反了开放-封闭原则。我们可以把服装作为一个抽象接口,西装、皮鞋这些服装作为它的实现类,然后分别调用每一个实现类来“穿衣”,但这样的问题是把所需的功能按正确的顺序串联起来进行控制的任务暴露给了调用者。所以我们可以采用装饰模式。

如果只有一个ConcerteComponent类而没有抽象的Component类,那么Decorator类可以是ConcerteComponent的一个子类。同样道理,如果只有一个ConcerteComponent类,那么就没有必要建立一个单独的Decorator类,而可以把ConcerteComponent和Decorator的责任合并成一个类。在本例中,可以以人作为ConcerteComponent,服饰类Decorator直接继承ConcerteComponent,不再需要Component类。代码如下:

public class Person {
	public Person() {
	}

	private String name;

	public Person(String name) {
		this.name = name;
	}

	public void Show() {
		System.out.println("装扮的" + name);
	}
}

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();
		}
	}
}

public class BigTrouser extends Finery {
	@Override
	public void Show() {
		System.out.println("垮裤 ");
		super.Show();
	}
}

public class Sneakers extends Finery {
	@Override
	public void Show() {
		System.out.print("破球鞋 ");
		super.Show();
	}
}

public class TShirts extends Finery {
	@Override
	public void Show() {
		System.out.print("大T恤 ");
		super.Show();
	}
}

public class Suit extends Finery {
	@Override
	public void Show() {
		System.out.print("西装 ");
		super.Show();
	}
}

public class Tie extends Finery {
	@Override
	public void Show() {
		System.out.print("领带 ");
		super.Show();
	}
}

public class LeatherShoes extends Finery {
	@Override
	public void Show() {
		System.out.print("皮鞋 ");
		super.Show();
	}
}

测试类和结果如下,我们可以看到只要具体装饰类迭代地设上一个装饰为decorate,就能完成整个人的装扮,可以理解为不断对一个人添加修饰词。如果要新增新的修饰类,只需要新增Finery的子类。

public class FineryTest {
	public static void main(String[] args) {
		Person xc = new Person("小菜");

        System.out.println("第一种装扮:");

        Sneakers pqx = new Sneakers();
        BigTrouser kk = new BigTrouser();
        TShirts dtx = new TShirts();

        pqx.Decorate(xc);
        kk.Decorate(pqx);
        dtx.Decorate(kk);
        dtx.Show();

        System.out.println("\n第二种装扮:");

        LeatherShoes px = new LeatherShoes();
        Tie ld = new Tie();
        Suit xz = new Suit();

        px.Decorate(xc);
        ld.Decorate(px);
        xz.Decorate(ld);
        xz.Show();

        System.out.println("\n第三种装扮:");
        Sneakers pqx2 = new Sneakers();
        LeatherShoes px2 = new LeatherShoes();
        BigTrouser kk2 = new BigTrouser();
        Tie ld2 = new Tie();

        pqx2.Decorate(xc);
        px2.Decorate(pqx);
        kk2.Decorate(px2);
        ld2.Decorate(kk2);

        ld2.Show();
	}
}

第一种装扮:
T 垮裤 破球鞋 装扮的小菜

第二种装扮:
西装 领带 皮鞋 装扮的小菜

第三种装扮:
领带 垮裤 皮鞋 破球鞋 装扮的小菜

装饰模式时为已有功能动态地添加更多功能的一种方式。在一开始的设计中,当系统需要新功能的时候,是向旧的类中添加新的代码。这些新加的代码通常装饰了原有类的核心职责或主要行为。但这种做法的问题在于,在主类中加入了新的字段,新的方法和新的逻辑,从而增加了朱磊的复杂度,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。而装饰模式提供了一个好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以运行时根据需要有选择地、按顺序使用装饰功能包装对象了。优点是:把类中的装饰功能从类中搬移去除,这样可以简化原有的类,有效地把类的核心职责和装饰功能区分开了。而且可以去除相关类中重复的装饰逻辑。最好保证装饰类之间彼此独立,避免装饰顺序产生影响。

参考资料

《大话设计模式》第6章