设计模式——装饰者模式(Decorator)

为什么要用装饰者模式?首先就抛出一个尖锐的问题。不得不继续吐槽我们泛滥使用的继承了。当频繁的继承使我们的代码成一条线的时候,或许就该考虑,怎么样在运行的时候扩展类的属性,或者说是装饰类,而不仅仅是在编译前期直接构思好继承逻辑。

继续引用经典的例子。带上场景,我们为一家急速发展的饮品店开发一款订单软件。

 一 初步设计订单系统框架

饮品作为订单的核心我们选择作为一个父抽象类,所有子类型饮品必须继承父类。作为饮品,我们需要标注它的价钱以及类型。

设计模式——装饰者模式(Decorator)_第1张图片

但是,如果饮品材料价钱修改/增加新的饮品,我们就不得不依次修改每个价钱/增加一个新的类,如果我们有100多种类饮品,一个一个修改,简直噩梦好吧。

也许这时候你会想,我为什么要设计这么多 类,直接使用实例变量和继承,就可以跟踪饮料的材料。

恩,接着画图。

设计模式——装饰者模式(Decorator)_第2张图片

这个思路是我们从父类(Beverage)入手,加上boolean属性,来确定子类饮品是否需要某种调料。然后cost()内通过材料的价钱和算出饮料价格。

但是呢,这种设计也存在着一些问题。

1.调料价格改变必须要修改代码。

2.一旦出现新的调料,我们不得不加上新方法,同时需要修改父类中的cost方法

二 修饰与改进

引入一个设计原则:类应该对扩展开发,对修改关闭。简单的说,就是允许我们的我们的类进行扩展,在不修改现有代码的情况  下,适应新的行为改变。

我们以DarkRoast举例,他的成分为Mocha和Whip,我们需要通过cost()计算所有调料和。我们的想法是所有的调料都继承一个类。一步一步的价格累加最终获得总价钱。

设计模式——装饰者模式(Decorator)_第3张图片

1 DarkRoast对象继承Beverage,拥有cost()方法。

2 添加第一种调料Mocha,建立第一个装饰者。

3 添加第二种调料Whip,建立第二个装饰者。

最最外围开始计算价钱,Whip的价钱为a,加上里面一层Mocha的价钱b,最里面就为DarkRoast价钱a+b,一层一层递进。

我们发现装饰者(调料)和被装饰者拥有同一个方法cost(),也就是说,他们也许拥有同一个父类,但是基于调料的属性,我们应该使调料继承调料类,调料总类和饮品类拥有同一个父类。

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

我们用一个网上的标准类图来定义一下装饰者模式。

设计模式——装饰者模式(Decorator)_第4张图片

我们的成分类CondimentDececorator于各种饮品处于同一位置,也就是说,装饰者于被装饰者必须是一样的类型。通过继承的方式,不是继承行为,而是为了达到类型匹配。当我们需要新的饮料类型时,不是通过继承获得行为,而是通过组建间的组合获得新的饮料。

既然类图已经确定,直接上代码吧。

1.首先我们定义饮料父类。

public abstract class Beverage {//父类作为一个抽象类呈现
	String descriptionString = "未知饮料";

	//类型描述父类实现
	public String getDescription() {
		return descriptionString;
	}
	
	//子类需要实现的抽象方法
	public abstract double cost();

}

2.实现调料的父类,必须能够替代饮料类,所以继承自饮料类,从而获得同样的类型。

public abstract class CondimentDecorator extends Beverage {

	// 调料材料子类必须实现的抽象方法
	public abstract String getDescription();

}

3.关于2个基类已经定义完毕,我们设置下具体的被装饰着(具体的饮料类型)。具体饮料必须需要描述,让客人明白这是什么饮料,也必须定义价格,让客人可以付款。

public class HouseBlend extends Beverage {
	public HouseBlend() {
		description = "House Blend Coffee";
	}

	@Override
	public double cost() {
		// TODO Auto-generated method stub
		return 5;
	}

}

仔细推敲下我们写的类图,我们已经有了抽象组件Beverage,具体组件HouseBlend,也有了抽象装饰者CondimentDecorator,我们需要实现具体的装饰者——具体的材料。

/*饮料配料-摩卡*/
public class Mocha extends CondimentDecorator {
	Beverage beverage;
	double myCost = 20;

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

	@Override
	public String getDesCription() {
		return beverage.getDescription() + ",Mocha";
	}

	@Override
	public double cost() {
		return myCost + beverage.cost();
	}

}
/*饮料配料-烤培*/
public class Whip extends CondimentDecorator{
	Beverage beverage;
	double myCost = 20;

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

	@Override
	public String getDescription() {
		return beverage.getDesCription() + ",Whip";
	}

	@Override
	public double cost() {
		return myCost + beverage.cost();
	}

}

关于构造方法,我们需要引入被修饰对象的实例变量,所以就作为构造参数输入了。

关于description和cost方法,我们都是采用获取被修饰对象的属性,然后才去累加的方法作为新的输入,这样,新的属性能够一直叠加下去(比如计算价格)。也就是说我们计算Mocha的价钱,我们首先获取被装饰对象的价钱,然后加上自身价值,算出总的金额。

我们用一段测试代码测试下我们的订单。

public class TestBeverage {

	public static void main(String args[]) {
		//不加调料的HouseBlend
		Beverage beverage = new HouseBlend();
		System.out.println(beverage.getDescription()+"     $"+beverage.cost());
		
		//加入调料的HouseBlend
		Beverage beverage2 = new HouseBlend();
		beverage2=new Soy(beverage2);
		beverage2=new Mocha(beverage2);
		System.out.println(beverage2.getDescription()+"    $"+beverage2.cost());
	}

}

结果如下:

设计模式——装饰者模式(Decorator)_第5张图片

三 典型的装饰者I/O

据说,java.io包内的类很多都是装饰者,我们常见的文件读写会用到的哟~下面举个典型的例子。

设计模式——装饰者模式(Decorator)_第6张图片

BufferInputStream,LineNumberInputStream都是扩展自抽象的装饰类FileInputStream。

设计模式——装饰者模式(Decorator)_第7张图片

java.io符合装饰者模式所有特点。顺着看一看,比如Writer和Reader流和输入输出流十分类似。但是这些我们也有很多困扰,IO包中的小类实在太多了,我到现在记不清,有时候还是避免不了麻烦百度童鞋。

你可能感兴趣的:(笔记,Java)