设计模式之装饰模式

1 引子

大家都喝过奶茶,我们知道奶茶可以根据自己的喜好来加一些配料,如珍珠、糖、红豆等使得奶茶更好喝。
在这可以简单方式实现上述过程:
奶茶类MilkyTea:可以加各种配料组合成一杯可口的奶茶

public class MilkyTea {
	public MilkyTea(){
		System.out.println("只有奶茶,什么也没加...");
	}
	
	public void addPearl(){
		System.out.println("加一份珍珠....");
	}
	
	public void addRedBean(){
		System.out.println("加一份红豆...");
	}
	
	public void addSugar(){
		System.out.println("加半糖...");
	}
}

门店类Client:根据客户的需要定制一杯奶茶

public class Drink {
	public static void main(String[] args) {
		System.out.println("================第一份奶茶==============");
		MilkyTea milkyTea = new MilkyTea();
		milkyTea.addPearl();
		milkyTea.addRedBean();
		System.out.println("================第二份奶茶==============");
		MilkyTea milkyTea2 = new MilkyTea();
		milkyTea2.addPearl();
		milkyTea2.addSugar();
	}
}

运行结果:

================第一份奶茶==============
只有奶茶,什么也没加...
加一份珍珠....
加一份红豆...
================第二份奶茶==============
只有奶茶,什么也没加...
加一份珍珠....
加半糖...

对于上面的过程,有以下几个缺点:
①如果新进一种配料,则需要在MilkyTea类中新添加一个方法;
②并非所有的MilkyTea类都可以加糖,即可以调配的MilkyTea类需要多种;
可以看出,MilkyTea类的可扩展性差,且新增会违背“开闭原则”;究其原因,奶茶与配料的耦合度较高,要想满足类的设计原则,提高可扩展性,则需要把奶茶类与各种配料进行解耦,使得客户想加什么配料都能加,且新加的配料不会影响原有的奶茶。
为了解决以上的缺点,构造新的生产奶茶的过程。
为了奶茶好喝,需要加各种配料:

// 接口
public interface Enjoy {
	void addBatching();
}

原味奶茶MilkyTea类:

// 为了好喝 奶茶还需要加点配料
public class MilkyTea implements Enjoy {
	@Override
	public void addBatching() {
		System.out.println("只有奶茶,没加任何配料....");
	}
}

抽象配料Batching类:

// 抽象配料类
public abstract class Batching implements Enjoy {
	Enjoy enjoy;
	public Batching(Enjoy enjoy){
		this.enjoy = enjoy;
	}
	@Override
	public void addBatching(){
		enjoy.addBatching();
	}
}

下面是各种配料
珍珠配料Pearl类:

// 珍珠配料
public class Pearl extends Batching {

	public Pearl(Enjoy enjoy) {
		super(enjoy);
	}
	@Override
	public void addBatching(){
		super.addBatching();
		System.out.println("加一份珍珠....");
	}
}

红豆配料RedBean类:

// 红豆配料
public class RedBean extends Batching {

	public RedBean(Enjoy enjoy) {
		super(enjoy);
	}
	@Override
	public void addBatching(){
		super.addBatching();
		System.out.println("加一份红豆....");
	}
}

糖配料Sugar类:

// 糖配料
public class Sugar extends Batching {

	public Sugar(Enjoy enjoy) {
		super(enjoy);
	}
	@Override
	public void addBatching(){
		super.addBatching();
		System.out.println("加半糖...");
	}
}

暂且就三种配料,下面是客户类的需求:

public class Client {
	public static void main(String[] args) {
		System.out.println("==============第一份奶茶==============");
		MilkyTea milkyTea = new MilkyTea();
		Enjoy sugar = new Sugar(milkyTea);
		Enjoy pearl = new Pearl(sugar);
		Enjoy redBean = new RedBean(pearl);
		redBean.addBatching();
		System.out.println("===============第二份奶茶=============");
		MilkyTea milkyTea2 = new MilkyTea();
		Enjoy pearl2 = new Pearl(milkyTea2);
		pearl2.addBatching();
	}
}

运行结果:

==============第一份奶茶==============
只有奶茶,没加任何配料....
加半糖...
加一份珍珠....
加一份红豆....
===============第二份奶茶=============
只有奶茶,没加任何配料....
加一份珍珠....

达到的效果跟第一种方式一样,但是过程却不一样。很明显,第二种的灵活性要比第一种高,且能满足各种不同客户的要求。类似于这种结构的模式称为装饰模式。上面过程的uml如下:
uml

2 装饰模式的原理

《大话设计模式》中这样定义装饰模式:动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。
这里是给生成的对象,按需添加职责,在上面的例子就是给milkyTea对象添加各种配料;给对象添加职责比通过父子类添加职责更灵活。子类继承父类的职责,且子类可以添加额外的职责,这种新添加的职责使得子类与父类耦合在一起,无法灵活扩展,只有不停地新增子类才能满足实际需求。装饰模式的UML类图:
装饰模式
装饰模式UML类图中的角色有:
“抽象构建角色Component”:抽象接口,是用来规范准备接收附加责任的对象。
“具体构建角色ConcreteComponent”:定义一个将要接收附加责任的类。
“装饰角色Decorator”:持有一个构建对象的实例,并定义一个与抽象构建接口一致的接口或者抽象类。
“具体装饰角色ConcreteDecoratorA/ConcreteDecoratorB”:负责给构建对象增加额外的责任。
注意:装饰角色和具体构建角色实现同一个接口,是为了继承类型,而不是行为。意思就是说,装饰角色和具体构建角色在客户端具有共同的类型,使得各种配料对客户端完全透明。如果装饰角色没有实现Component接口,则客户端需要构造具体的装饰角色对象,且装饰角色繁杂。

3 装饰模式的特点

装饰模式有哪些优点呢?
装饰模式具有较强的灵活性,遵守类的设计原则,即“开闭原则”。另外,可动态的给构建实例增加新的“装饰”,且不影响构建实例本身;相比于继承关系,装饰模式扩展性更好,可随意把 “装饰实例”贴给构建实例,而继承在运行前就确定了类的功能。
装饰模式有哪些缺点?
装饰模式设计上较难理解,特别是接口类。
注意:如果只有一个ConcreteConponent类而没有抽象的Component类,那么Decorator类可以是ConcreteComponent的一个子类。同样的道理,如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。

4 装饰模式的适用场景

装饰模式在java的I/O库中有运行,具体可看下面参考资料第一个链接。

5 参考资料

《大话设计模式》
http://www.cnblogs.com/java-my-life/archive/2012/04/20/2455726.html
https://www.jianshu.com/p/d7f20ae63186
https://blog.csdn.net/l_bestcoder/article/details/66967740

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值