设计模式——03 装饰者模式

3 Decorator Pattern(装饰者模式)

3.1设计原则一

类应该对扩展开放,对修改关闭

         前言:装饰者模式主要是为了解决继承滥用的问题,以下将使用对象组合的方式做到在运行时装饰类。

1)案例分析一:

         REQ1星巴克咖啡店咖啡种类扩展飞快,Vander作为其老板,准备尽快更新订单系统来满足这一发展。原先的设计如下:

分析随着饮品的发展,每种饮料都可以自由搭配,而且本身饮料也很多,除了咖啡之外,鸳鸯、奶茶、可乐、雪碧、酸奶、豆浆等等,而且调料还可以自由选择。使用不同的调料需要付不同的价格,例如一小份牛奶加入咖啡中,加收1块钱等等。如果继续按照上述的设计继续,则继承Beverage抽象类的饮料将非常多,例如coffeewithonemilk,milkTeaWithSteamAndSoy等等,这样能产生无数的搭配,并且牛奶价格上升之后,每个涉及到牛奶的类的cost函数还需要修改,这简直就是噩梦。

解决方法1:Vander 就开始设计了

         超类cost()将计算所有调料的价格,子类覆盖的cost()方法扩展超类的功能,把指定的饮料类型的价钱也加上。

public class Beverage {

	private String desc;
	
	private boolean milk;
	
	private boolean soy;
	
	private boolean mocha;
	
	private boolean whip;

	public boolean hasMilk() {
		return this.milk;
	}
	
	public boolean hasSoy() {
		return this.soy;
	}
	
	public boolean hasMocha() {
		return this.mocha;
	}
	
	public boolean hasWhip() {
		return this.whip;
	}
	
	public String getDesc() {
		return desc;
	}

	public void setDesc(String desc) {
		this.desc = desc;
	}
	
	public void setMilk(boolean milk) {
		this.milk = milk;
	}

	public void setSoy(boolean soy) {
		this.soy = soy;
	}

	public void setMocha(boolean mocha) {
		this.mocha = mocha;
	}

	public void setWhip(boolean whip) {
		this.whip = whip;
	}

	public float cost() {
		float flavourCost = 0.0f;
		if(hasMilk()) {
			flavourCost = flavourCost + 1.0f;
		} 
		if(hasSoy()) {
			flavourCost = flavourCost + 2.0f;
		}
		if(hasMocha()) {
			flavourCost = flavourCost + 3.0f;
		}
		if(hasWhip()) {
			flavourCost = flavourCost + 4.0f;
		}
		return flavourCost;
	}
	
}

public class DarkRoast extends Beverage {

	private float cost;
	
	public float getCost() {
		return cost + super.cost();
	}

	public void setCost(float cost) {
		this.cost = cost;
	}
	
}

存在的问题:

         这个方法出现了以下4个问题:

         1、当调料价格改变的时候需要修改Beverage类的代码。

         2、一旦有新的调料,需要加上新的方法,并改变超类中的cost()方法。

         3、如果有新的饮料(Tea),对这些饮料而言,某些调料(如soy、stream等)可能并不适合,但是这个设计方式中,Tea子类仍将继承那些不合适的方法,例如:hasSoy()(加入豆浆)

         4、顾客万一不只是要一份摩卡,想加入两份摩卡调料,上述的方法根本无法应对。     

         解决方法3(使用装饰者模式):

         REQ2:首先有这么一种需求,顾客买了一杯DarkRoast,想加Mocha,然后再加奶泡Whip,最后要计算这杯DarkRoast的金额。

装饰者模式可以用下面的图来说明,该图上可以看到Whip包裹着Mocha,而Mocha又包裹了DarkRoast,并且这三个类的基类都是Beverage,DarkRoast继承自Beverage,且有一个用来计算费用的cost()方法,Mocha对象是一个装饰者,它的类型反映了它所装饰的对象;Whip也是一个装饰者,所以它也反映了DarkRoast类型,并包括一个cost()方法。

最后到结账的时候,先调用最外层的Whip,得到了Whip的价格,然后再调用Mocha的cost,此时Whip的价格传给了Mocha,这样Mocha再加上自己的价格,现在就得到了Mocha+Whip的价格,然后再调用DarkRoast的价格,最后就得到了这杯咖啡的价格。这样相当于就做到“在运行时决定类的行为”。

        

    这里要说明的是,Beverage可以用接口也可以用抽象类,若需要加入属性的话,就使用抽象类,若不需要属性则可以使用接口,方便日后代码可以extends其他的类。

以下是关键代码:

Beverage beverage = new DarkRoast();
beverage = new Whip(beverage);
beverage = new Mocha(beverage);

public class Mocha implements CondimentDecorator {

	private Beverage beverage;
	
	public float cost() {
		return this.beverage.cost() + 1.0f;
	}
	
	public Mocha(Beverage beverage) {
		this.beverage = beverage;
	}

	public String getDesc() {
		return this.beverage.getDesc() + " with " + "Mocha";
	}

}

REQ3:那么问题来了,现实的Java世界中有哪些用了装饰者模式呢,你是否看过这样的一句new LineNumberInputStream(new BufferedInputStream(new FileInputStream)),这句话看似很复杂,其实这就是典型的装饰者模式,FileInputStream是被装饰的“组件”,Java I/O程序库提供了几个组件,包括了FileInputStream、StringBufferInputStream、ByteArrayInputStream… …等。这些类都提供了最基本的字节读取功能。

BfferedInputStream是具体的装饰者,加入了两种行为,利用缓存输入来改进性能,用readline()方法(用来一次读取一行文本输入数据)来增强接口。

LineNumberInputStream也是一个具体的装饰者,它加上了计算行数的功能。

         下面进行一个小练习,写一个IO装饰者来讲输入流中所有的大写字母转成小写。

public class LowcaseInputStream extends FilterInputStream {

	public LowcaseInputStream(InputStream in) {
		super(in);
	}
	
	public int read() throws IOException {
		int c = super.read();
		return ( c == -1? c : Character.toLowerCase((char)c));
	}
	
	public int read(byte[] b, int offset, int len) throws IOException {
		int result = super.read(b, offset, len);
		for(int i = offset; i < offset + result; i++) {
			b[i] = (byte)Character.toLowerCase((char)b[i]);
		}
		return result;
	}

}

public class Main {

	public static void main(String[] args) {
		InputStream inputStream;
		int c;
		try {
			inputStream = new FileInputStream("test.txt");
			inputStream = new BufferedInputStream(inputStream);
			inputStream = new LowcaseInputStream(inputStream);
			while((c = inputStream.read()) >= 0) {
				System.out.print((char)c);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
}

    装饰者模式的缺点:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能造成使用此API程序员的困扰。但是其实了解了装饰者的原理,就可以容易地辨别出他们的装饰者类是如何组织的,以方便用包装方式取得想要的功能。

面向对象基础

         抽象、封装、多态、继承

四大原则

设计原则一:封装变化

设计原则二:针对接口编程,不针对实现编程。

         设计原则三:多用组合,少用继承。

         设计原则四:为交互对象之间的松耦合设计而努力

设计原则五:对扩展开放,对修改关闭

 

模式

装饰者模式:动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择。

 

最后献上此次设计的源码,源码中包括了一些起初错误的实现,以及后期经过思考后正确的实现,在此处出现的所有源码均有实现,有需要的小伙伴可以下载来运行一下,首先先自己进行设计,然后再参考,这样才能加深观察者模式的理解。

猜你喜欢

转载自blog.csdn.net/Vander1991/article/details/83857696