通过阅读本篇文章,可以给喜欢使用继承的开发人员提供一种新的思路。我们将会了解滥用继承带来的不良后果,同时也会介绍比继承更合理的实现方式。
利用继承设计子类的行为,是在编译时期静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。
假设现在有一家知名的咖啡店,因为扩展速度太快,希望你能帮他们重新设计一套饮料销售系统,满足当下的需求。
原先的类设计结构图如2-1所示
(2-1)
顾客在购买饮料时,通常会加入各种各样的调料,比如牛奶、Mocha,豆浆(Soy)。所以饮料系统需要考虑调料的问题。我们第一次尝试,如果调料不同就新定义一个新的饮料对象。设计结构图如图2-2所示
(2-2)
从图中我们可以看下,这种设计方式虽然可以实现功能,但是对象太多,如果新加了一种调料,有需要创建很多的饮料对象,而且原有的代码逻辑也需要更改。
能不能解决饮料对象太多的问题呢?利用继承和实例变量,把所有的调料都定义在超类中,并给这些调料设置属性。如果子类需要调料,那就给调料设置相应的属性。对应的结构图如图2-3所示
(2-3)
利用这种方法,虽然可以减少类的数量,但是所有的子类,都会继承基类中的调料,假设有些饮料不需要牛奶作为调料,但是饮料对象中仍然会有牛奶调料属性,这显然有点不太合理。
通过上文,我们已经认识到利用继承无法完全解决饮料系统对应的问题,比如:类数量太多,设计死板,基类的属性和方法并不适用于所有的子类。
我们以饮料为主体,然后在运行时以调料来”装饰“饮料。比方说如果顾客想要摩卡和奶泡深焙咖啡,那么要做的是:
1、那一个深焙咖啡(DarkRoast)对象
2、以摩卡(Mocha)对象装饰它;
3、以奶泡(Whip)对象装饰它;
4、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。
如何通过代码实现这种美妙的思想,我们首先来了解装饰者的定义和类图。
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代解决方案。
类结构图如2-4所示
(2-4)
我们利用装饰者模式,重新改造饮料系统的结构。如图2-5所示
(2-5)
代码实现
package cn.lzz.hf.third;
public abstract class Beverage {
protected double amount;
protected double price;
protected String description;
/**
* 描述
* @return
*/
public abstract String getDescriptions();
/**
* 计算价格
* @return
*/
public abstract Double cost();
protected double getAmount() {
return amount;
}
protected void setAmount(double amount) {
this.amount = amount;
}
protected double getPrice() {
return price;
}
protected void setPrice(double price) {
this.price = price;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
package cn.lzz.hf.third;
public class DarkRoast extends Beverage {
public DarkRoast() {
super();
// TODO Auto-generated constructor stub
}
public DarkRoast(double amount,double price,String description){
this.amount=amount;
this.price=price;
this.description=description;
}
@Override
public String getDescriptions() {
// TODO Auto-generated method stub
return this.description;
}
@Override
public Double cost() {
// TODO Auto-generated method stub
return this.amount*this.price;
}
}
package cn.lzz.hf.third;
/**
* 调味品装饰超类
* @author Administrator
*
*/
public abstract class CondimentDecorator extends Beverage{
protected Beverage beverage;
public CondimentDecorator(){
}
public CondimentDecorator(Beverage beverage){
this.beverage=beverage;
}
@Override
public String getDescriptions() {
// TODO Auto-generated method stub
StringBuilder decriiption=new StringBuilder();
decriiption=decriiption.append(this.description).append(",");
if(null!=this.beverage){
decriiption=decriiption.append(this.beverage.getDescriptions());
}
return decriiption.toString();
}
@Override
public Double cost() {
// TODO Auto-generated method stub
return this.price*this.amount+(this.beverage==null?0.0:this.beverage.cost());
}
protected Beverage getBeverage() {
return beverage;
}
protected void setBeverage(Beverage beverage) {
this.beverage = beverage;
}
}
package cn.lzz.hf.third;
public class Milk extends CondimentDecorator {
public Milk(double amount,double price,String description){
this.amount=amount;
this.price=price;
this.description=description;
}
public Milk(double amount,double price,String description,Beverage beverage){
this.amount=amount;
this.price=price;
this.description=description;
this.beverage=beverage;
}
}
package cn.lzz.hf.third;
public class Mocha extends CondimentDecorator {
public Mocha(double amount,double price,String description){
this.amount=amount;
this.price=price;
this.description=description;
}
public Mocha(double amount,double price,String description,Beverage beverage){
this.amount=amount;
this.price=price;
this.description=description;
this.beverage=beverage;
}
}
package cn.lzz.hf.third;
public class Soy extends CondimentDecorator {
public Soy(double amount,double price,String description){
this.amount=amount;
this.price=price;
this.description=description;
}
public Soy(double amount,double price,String description,Beverage beverage){
this.amount=amount;
this.price=price;
this.description=description;
this.beverage=beverage;
}
}
package cn.lzz.hf.third;
public class Whip extends CondimentDecorator {
public Whip(double amount,double price,String description){
this.amount=amount;
this.price=price;
this.description=description;
}
public Whip(double amount,double price,String description,Beverage beverage){
this.amount=amount;
this.price=price;
this.description=description;
this.beverage=beverage;
}
}
package cn.lzz.hf.third;
public class Test {
public static void main(String[] args) {
Beverage darkRoast=new DarkRoast(1,1.1,"焦炒咖啡");
darkRoast=new Milk(1,2,"牛奶", darkRoast);
darkRoast=new Mocha(2,3,"摩卡", darkRoast);
darkRoast=new Soy(1,4,"大豆", darkRoast);
darkRoast=new Whip(1,4,"泡沫", darkRoast);
System.out.println("description:"+darkRoast.getDescriptions());
System.out.println("totalPrice:"+darkRoast.cost());
}
}
description:泡沫,大豆,摩卡,牛奶,焦炒咖啡
totalPrice:17.1
装饰者模式意味着一群装饰者类,这些类用来包装具体组件。装饰者可以在被装饰者的行为前后加上自定义的行为,甚至可以将被装饰者的行为整个取代掉,而达到特定的目的。当然,装饰者模式会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。
详细内容可以参考《Head First 设计模式》。