设计模式三之装饰者模式(java)

这是我看Head first设计模式书籍之后想要总结的知识点,一方面是对自己学习的东西总结和提炼加强自己的理解和记忆,另一方面是给大家简化这本书,方便大家快速了解各种设计模式。

我想提醒大家的是,设计模式只是前人总结的一些经验套路,实际上还是要在开发项目中慢慢体会,不可成为设计模式的中毒患者,强行照搬设计模式的一些规则。


我们这次要讲解的设计模式是装饰者模式

我们举个栗子:

我们需要为连锁咖啡店开发一个更新订单系统,以合乎它们的饮料供应要求

价钱分为两种:一种是不同品种咖啡的钱 另一种是不同添加的调料的钱

最初的设计是这样子的:

Beverage(饮料)是一个抽象类,店内所提供的所有饮料都必须继承这个类

Beverage类中 属性: description用来描述饮料 getDescription()获得描述信息 cost()获得价格信息 还有一些其他的方法

所有不同的饮料都继承自Beverage


第一个思路:

 创造出添加不同调料配不同品种咖啡的类继承Beverage类,这样会使代码非常的冗杂,类简直是太多了

导致的问题: 

1. 如果配料牛奶的价格增加了,要把添加了牛奶的咖啡的所有类都改变一下,这真的很复杂

2.当要增加一种口味的调料时,又要创造成几何增长的类

违反设计规则: 封装发生变化的代码,多用组合,少用继承


第二种思路:

在基类Beverage直接使用布尔实例变量来代表是否加上不同种类的调料,使用继承来继承不同set和get方法来设置是否添加调料

Beverage属性: description milk soy mocha whip 方法: getDescription() cost() hasMilk() setMilk() hasSoy() setSoy()等一系列getter()和setter()方法 

使用不同品种咖啡来继承基类

出现的问题:

1.调料价钱的改变会使我们更改现有的代码

2.一旦出现新的调料,我们就需要加上新的方法(hasXxx()、setXxx()),并改变超类中的cost()方法(添加if判断是否加入了新调料,并调整返回的价格)

3.一旦开发除了新的饮品,比如茶,一些继承下来的调料并不适合,也继承了不适合的方法

4.如果客人想要添加双份的调料,这样调料价格就应该为2倍,这时候又该怎么办


得出的感悟:

利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而, 如果能够利用组合

的做法扩展对象的行为,就可以在运行时动态地进行扩展。利用动态地组合对象,可以写新的代码添加新的功能,而无须修改现有代码,引进的bug或产生以外副作用的机会将大幅度减小。

设计原则:允许类容易拓展,在不修改现有代码的情况下,就可搭配新的行为。这样的设计具有弹性可以应对改变,

可以接受新的功能来应对改变的需求

即类应该对扩展开发,对修改关闭(如设计模式二之观察者模式,通过加入新的观察者,我们可以在任何时候拓展Subject(主题),而且不需向主题中添加代码) 应用这个原则的时候要将重点放在设计中最有可能改变的地方,胡乱用会增加代码的复杂性和阅读障碍。


下面是我们的装饰者模式出厂了:

装饰者模式: 动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案

装饰者模式的构成:

1.Component(组件类)抽象类(或接口), 方法有methodA(),methodB()等方法 

2.ConcreteComponent(组件具体实现类/被装饰者)是我们将要动态地加上新行为的对象,它继承自Component

3.Decorator(装饰者类) 继承自Component类,这是装饰者共同实现的抽象类(也可以是接口)

4.ConcreteDecoratorA等具体的装饰者类(也就是配料)

属性Component wrappedObj(用来记录所装饰的事物) 方法methodA() methodB() 其他方法

注意:  Decorator抽象类是为了使ConcreteDecoratorA和被装饰者具有相同的类型,继承

达到"类型匹配"的目的,而不是利用继承获得"行为" ,"行为"是从被装饰者对象中获得的。


下面就是使用了装饰者模式的设计方案:

我们以饮料为主体,然后在运行时以调料来“装饰”(decorate)饮料。

饮料就是组件抽象类,我们可以生成一系列不同品种的饮料组件;而装饰者就是不同的配料


直接看代码部分:

Beverage饮料组件抽象类以及调料抽象类(继承组件抽象类)

public abstract class Beverage{
	String description = "Unknown Beverage";

	public String getDescription(){
		return description;
	}

	public abstract double cost();
}

public abstract class CondimentDecorator extends Beverage{
	public abstract String getDescription(); //所有调料装饰者必须重新实现getDescription()方法
}


各种组件饮料的具体类(Espresso类、HouseBlend类)

public class Espresso extends Beverage{

	public Espresso(){
		description = "Espresso";
	}

	public double cost(){
		return 1.99;
	}
}

public class HouseBlend extends Beverage{

	public Espresso(){
		description = "HouseBlend";
	}

	public double cost(){
		return .89;
	}
}


装饰者调料具体实现类Mocha Soy Whip

public class Mocha extends CondimentDecorator{

	Beverage beverage;

	public Mocha(Beverage beverage){
		this.beverage = beverage;
	}

	public String getDescription(){
		reuturn beverage.getDescription() + ", Mocha";
	}

	public double cost(){
		return .20 + beverage.cost();
	}
}

public class Soy extends CondimentDecorator{

	Beverage beverage;

	public Soy(Beverage beverage){
		this.beverage = beverage;
	}

	public String getDescription(){
		reuturn beverage.getDescription() + ", Soy";
	}

	public double cost(){
		return .15 + beverage.cost();
	}
}

public class Whip extends CondimentDecorator{

	Beverage beverage;

	public Whip(Beverage beverage){
		this.beverage = beverage;
	}

	public String getDescription(){
		reuturn beverage.getDescription() + ", Whip";
	}

	public double cost(){
		return .3 + beverage.cost();
	}
}

测试代码

public class StartbuzzCoffe{

	public static void main(String args[]){
		Beverage beverage = new Espresso();
		System.out.println(beverage.getDescription() 
			+ " $" + beverage.cost());

		Beverage beverage2 = new DarkRoast();
		//这里我们点了两份Mocha配料
		beverage2 = new Mocha(beverage2);
		beverage2 = new Mocha(beverage2);
		beverage2 = new Whip(beverage2);
		System.out.println(beverage2.getDescription() 
			+ " $" + beverage2.cost());
	}
}
这种模式的缺点:

1.如果代码针对特定种类的具体组件,做一些特殊的事,如打折,一旦用包装着包装它,就会造成类型改变

2.采用装饰者在实例化组件时,会增加代码的复杂度,一旦使用装饰者模式,不只需要实例化组件,还要把此组件

包装进装饰者中,如果有多个调料,那么就要包装多少次。 我们以后会谈到Factory(工厂模式),和生成器模式(Builder)

,可以解决此问题

3.装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。


下面我们讲解java Api中关于装饰者模式的应用

java.io包装关于输入输出流的类大都用了装饰者模式

比如:

抽象组件: InputStream

具体组件: FileInputStream、StringBufferedInputStream、ByteArrayInputStream等为抽象组件子类

抽象装饰者: FilterInputStream为抽象组件子类

具体装饰者: PushbackInputStream、BufferedInputStream(加入了缓冲输入来改善性能、用一个readline()方法读取一行文本输入数据)、DataInputStream、LineNumberInputStream(增加计算行数的能力)为抽象装饰者子类


实现自己的Java I/O装饰者

将所有大写字符转换成小写,例如将"HELLO WORLD"转换成"hello world"

//FilterInputStream是所有InputStream组件的抽象装饰者
public class LowerCaseInputStream extends FilterInputStream{

	public LowerCaseInputStream(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 < result + offset; i++){
			b[i] = (byte)Character.toLowerCase((char)b[i]);
		}
		return result;
	}
}

public class InputTest{
	public static void main(String[] args) throws IoException{
		int c;
		try{
			InputStream in = new LowerCaseInputStream(
				new BufferedInputStream(
				new FileInputStream("test.txt")));

			while((c = in.read()) >= 0){
				System.out.print((char)c);
			}

			in.close();
		} catch(IOException e){
			e.printStackTraace();
		}
	}
}

下一次我们将介绍工厂模式啦!

猜你喜欢

转载自blog.csdn.net/qq_32252957/article/details/80487827