设计模式(四)一文搞明白装饰者模式

关于装饰者模式的定义,我就直接引用Head First了:装饰者模式动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案 。 其实装饰者模式的重点在于给对象动态的附加职责,通过对象组合的方式,运行时装饰对象,在不改变任何底层代码的情况下,给现有对象赋予新的职责。

装饰者模式

现在我们要为卖煎饼的大妈设计一套系统,让大妈能更好的算账收钱。大妈主要经营煎饼(7元),并可以往其中添加配料烤肠(1元),鸡蛋(1元),如果生意红火,可能会考虑扩大经营其他小吃或添加配料种类。现需要设计出一套系统,以便快速计算出每位顾客所购小吃的价格。

这是一个食品类接口。同样,要符合针对接口编程,不针对实现编程的OO原则。

public interface ISnack {
	// 食物的描述
	String getDescription();
	// 食物的价格
	double cost();
}

这是一个配料类接口。配料类接口IDecoratorSnack继承自ISnack接口,因为需要两接口的实现类(装饰者与被装饰对象)具有相同的超类型,只有这样IDecoratorSnack接口才能取代ISnack接口。

/**
 * 这是配料类的接口。
 * 
 * <p>装饰者与被装饰者需要具有相同的接口,
 * 这样用户使用装饰者对象才会和使用原始对象一样。
 */
public interface IDecoratorSnack extends ISnack {
	// 可根据需要扩展属性,如配料大份,小份等
}

这是具体的食品-煎饼类

public class PancakeSnack implements ISnack {
	@Override
	public String getDescription() {
		return "煎饼";
	}
	
	@Override
	public double cost() {
		return Price.PancakeSnack.price;
	}
}

食品类(被装饰者)既可以单独使用,也可以被配料类(装饰者)包着使用,因为装饰者和被装饰者对象具有相同的超类型,所以在任何需要原始对象(被包装的)的场合,都可以用装饰过的对象替代它

如果,我想要一份加鸡蛋和烤肠的煎饼价格是多少?单价类见下:

public enum Price {
	//鸡蛋、烤肠、煎饼
	Egg(1.0), Sausage(1.0), PancakeSnack(7.0);
	
	double price;

	private Price(double price) {
		this.price = price;
	}
}

这是我们的具体配料鸡蛋类

public class Egg implements IDecoratorSnack {
	// 持有被修饰的对象
	ISnack iSnack;
	
	public Egg(ISnack iSnack) {
		this.iSnack = iSnack;
	}
	
	@Override
	public String getDescription() {
		return iSnack.getDescription() + ",加鸡蛋";
	}
	
	@Override
	public double cost() {
	    // 被修饰对象的价格 + 鸡蛋价格
		return iSnack.cost() + Price.Egg.price;
	}
}

这是具体的烤肠配料类

public class Sausage implements IDecoratorSnack{
	//持有被修饰对象的引用
	ISnack iSnack;
	
	public Sausage(ISnack iSnack) {
		this.iSnack = iSnack;
	}
	
	@Override
	public String getDescription() {
		return iSnack.getDescription() + ",加香肠";
	}
	
	@Override
	public double cost() {
		// 被修饰对象的价格 + 烤肠价格
		return iSnack.cost() + Price.Sausage.price;
	}
}

每个装饰者都持有一个被装饰者的对象,这个被装饰者对象不一定是原始对象,也可能是被包装了多层的对象。通过这种组合,加入了新的行为。符合对扩展开放,对修改关闭的OO原则

我们来收钱吧。我们能够发现,我们能够使用被装饰的对象就像使用原始对象一样,这归功于装饰者与被装饰者具备同样的接口。同样一个原始对象是能够被包装多层的,而在使用者的眼里,它只是一个被赋予了新功能的原始对象而已。

扫描二维码关注公众号,回复: 11521545 查看本文章
public class DecoratorPatternTest {
	
	public static void main(String[] ags) {
        // 煎饼 + 鸡蛋 + 烤肠
		ISnack snackOneISnack = new PancakeSnack();
		snackOneISnack = new Egg(snackOneISnack);
		snackOneISnack = new Sausage(snackOneISnack);
		// 这样就能够打印出,我们的(煎饼 + 鸡蛋 + 烤肠)价格和描述了
		System.out.println(snackOneISnack.getDescription() + "价格:" + snackOneISnack.cost() + "元");
	}
}

而且扩展起来方便,我们随时加入不同种类小吃和配料类。如我们现在要加一种烤冷面类

public class ColdRoastSnake implements ISnack{
	@Override
	public String getDescription() {
		return "烤冷面";
	}
	
	@Override
	public double cost() {
		return Price.ColdRoastSnake.price;
	}
}

我们再添加一个鸡里脊肉类

public class Chicken implements IDecoratorSnack{	
	ISnack iSnack;
	
	public Chicken(ISnack iSnack) {
		this.iSnack = iSnack;
	}
	
	@Override
	public String getDescription() {
		return iSnack.getDescription() + ",加里脊肉";
	}
	
	@Override
	public double cost() {
		return iSnack.cost() + Price.Chicken.price;
	}
}

好了,如果用户购买烤冷面+鸡蛋+里脊肉,价格该多少呢?

public class DecoratorPatternTest {
	
	public static void main(String[] ags) {
        // 烤冷面 + 鸡蛋 + 里脊肉
		ISnack snackOneISnack = new ColdRoastSnake();
		snackOneISnack = new Egg(snackOneISnack);
		snackOneISnack = new Chicken(snackOneISnack);
		// 这样就能够打印出,我们的(烤冷面 + 鸡蛋 + 里脊肉)价格和描述了
		System.out.println(snackOneISnack.getDescription() + "价格:" + snackOneISnack.cost() + "元");
	}
}

需要注意的是,通过利用装饰者模式,会造成设计中产生大量的小类,如果过度使用,会使程序变得很复杂。另外可能还会出现类型问题,如果把代码写成依赖于具体的被装饰者类型,不针对抽象接口进行编程,那么就会出现问题。

猜你喜欢

转载自blog.csdn.net/MingJieZuo/article/details/106402565