嘿嘿嘿,你是不是很喜欢用继承呢?感觉没什么事情是一个“爸爸”类搞不定的,有的话就两个,快来跟我看看这个模式吧,它能让你“断奶”,给爱用继承的人一个全新的设计眼界。
直奔主题,你是否有听说过组合呢?你是否觉得继承往往有的时候没你想象的那么好用呢?如果有,你又是否尝试自己解决这个问题呢?让我们来看个例子~~~~
假设我们有家咖啡店,设计如下
饮料(Beverage)基类,有4个子类,分别是综合咖啡(HouseBlend),深焙咖啡 (DarkRoast),低咖啡因咖啡 (Decaf),(Espresso)浓缩咖啡,他们都是子类,继承了cost支付方法。
嗯,看似目前没啥问题,假设我需要改变点需求呢?
喝咖啡可以加各种各样调料的,例如加牛奶,豆奶,摩卡,小饼干等等,而且每种饮料根据添加的调料不同还要再加对应的调料费用,那么这些使用继承的话。。。。。。恐怕会出现以下这样的情况。。
想想以前这样的设计,是不是有种细思极恐的感觉。。
当然聪明一点的会这样设计,这里我就不自己画了,直接贴出来图例
其实这样的设计也非常有问题,跟上面"类爆炸"的设计没啥区别,父类和子类依赖性太高了,低耦合。
1:假设我在父类中添加了新的调料,那么所有子类都需要相应修改代码,加大了工作量。
2:在父类中改变cost方法,子类也需要跟着改变。。
总之,太滥用继承其实是给自己挖坑,继承往往有的时候不能设计出弹性和易扩展性的代码。这里就要说说和继承的“死对头”-->组合,而装饰者模式就充分运用了组合
设计原则一:类应该对扩展开放,对修改关闭。
注:这个原则刚开始听是感觉自相矛盾的,确实刚开始我也有这疑问,回过头看看观察者模式,通过加入新的“观察者”就相当于拓展开放了“主题”,而“主题”本身没有改变代码,对修改关闭了。不急请往下看。
依旧以咖啡店做例子
这里改变做法,以饮料为主体,然后用调料“装饰”饮料,
打个比方:如果顾客想要摩卡和奶泡深焙咖啡,要做的是:
1:拿一个深焙咖啡(HouseBlend)对象
2:以摩卡(Mocha)对象“装饰”它
3:以奶泡(Whip)对象“装饰”它
4:调用cost()方法,并依赖委托将调料价钱加上去
过程流程图如下(请多看几遍)
装饰者模式定义:动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
放出装饰者模型的类图(重点看)
然后下面是基于此模式下的咖啡店的类图
是不是感觉这个设计焕然一新。
下面我要好好打造我的咖啡店了
饮料抽象类
package Beverage;
/**
* 饮料抽象类
* 被装饰者类
* @author Joy
*
*/
public abstract class Beverage {
//饮料名称初始化
String description="未知饮料";
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
//价钱方法抽象化,不需要具体实现
public abstract double cost();
}
下面是四个咖啡类
package Beverage;
/**
* 深焙咖啡
*
* @author Joy
*
*/
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "深焙咖啡";
}
@Override
public double cost() {
return 0.99;
}
}
package Beverage;
/**
* 低咖啡因咖啡
*
*
*
*/
public class Decaf extends Beverage {
public Decaf() {
description = "低咖啡因咖啡";
}
@Override
public double cost() {
return 1.05;
}
}
package Beverage;
/**
*
* 浓缩咖啡类
*
*
*/
//继承饮料父类,因为它是饮料一种
public class Espresso extends Beverage {
//写一个构造器,将饮料名称重定义
public Espresso() {
description="浓缩咖啡";
}
//最后计算浓缩咖啡价格,先不算调料价钱,一杯浓缩咖啡1.99
@Override
public double cost() {
return 1.99;
}
}
package Beverage;
/**
* 综合咖啡类
*
*
*/
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "综合咖啡";
}
@Override
public double cost() {
return 0.89;
}
}
调料抽象类
package Condiment;
import Beverage.Beverage;
/**
* 调料抽象类 装饰者类
*
* @author Joy
*
*/
public abstract class CondimentDecorator extends Beverage {
// 描述
public abstract String getDescription();
}
下面是具体调料实现类
package Condiment;
import Beverage.Beverage;
/**
* 摩卡类,装饰者
*
* @author Joy
*
*/
// 摩卡是一个装饰者,让它继承装饰者父类
public class Mocha extends CondimentDecorator {
// 设定饮料变量记录饮料,也就是被装饰者
Beverage beverage;
// 设置构造器将被装饰者(饮料)被记录到实例变量beverage中
// 把饮料设置构造器参数,再有构造器将此记录到实例变量中
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
// 重写方法,将其饮料描述加上添加的调料
@Override
public String getDescription() {
return beverage.getDescription() + ",加摩卡";
}
// 重写方法,将饮料价钱+调料价钱
@Override
public double cost() {
return beverage.cost() + 0.2;
}
}
package Condiment;
import Beverage.Beverage;
/**
* 豆浆类,装饰者
* 内容与前面摩卡一样
* @author Joy
*
*/
public class Soy extends CondimentDecorator {
Beverage beverage;
public Soy(Beverage beverage) {
this.beverage=beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+",加豆浆";
}
@Override
public double cost() {
return beverage.cost()+0.15;
}
}
package Condiment;
import Beverage.Beverage;
/**
* 奶泡类,装饰者 内容与前面摩卡一样
*
* @author Joy
*
*/
public class Whip extends CondimentDecorator {
Beverage beverage;
public Whip(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ",加奶泡";
}
@Override
public double cost() {
return beverage.cost() + 0.1;
}
}
测试类
package TestMain;
import java.math.BigDecimal;
import Beverage.Beverage;
import Beverage.DarkRoast;
import Beverage.Espresso;
import Beverage.HouseBlend;
import Condiment.Mocha;
import Condiment.Soy;
import Condiment.Whip;
public class TestMain {
public static void main(String[] args) {
//要一杯浓缩咖啡,什么调料都不加
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + "\t$" + beverage.cost());
//要一杯深焙咖啡
Beverage beverage2=new DarkRoast();
//加两份摩卡
beverage2=new Mocha(beverage2);
beverage2=new Mocha(beverage2);
//加一份奶泡
beverage2=new Whip(beverage2);
System.out.println(beverage2.getDescription()+"\t$"+beverage2.cost());
//要一杯加豆浆,加摩卡,加奶泡的综合咖啡
Beverage beverage3=new HouseBlend();
beverage3=new Whip(beverage3);
beverage3=new Mocha(beverage3);
beverage3=new Soy(beverage3);
//这里对价钱保留两位小数四舍五入
double money=beverage3.cost();
BigDecimal b=new BigDecimal(money);
double f1=b.setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
System.out.println(beverage3.getDescription()+"\t$"+f1);
}
}
效果图:
要点:
1:继承属于扩展形式一种,但不见的是达到弹性设计的最佳方式,组合优于继承。
2:应该允许行为可以被拓展,而无需修改现有的代码。
3:装饰者模式意味着一群装饰者类,这些类用来包装具体组件。
4:装饰者类反映出被装饰组件类型。
5:可以使用无数个装饰者包装一个组件。
6:装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。
装饰者模式结束,这个模式是个很有用的模式,可以说是提高代码素养的关键一步,模拟代码已经全部放出,注释也写的比较清楚,若有不理解欢迎留言。
感谢你看到这里,装饰者模式部分结束,本人文笔随便,若有不足或错误之处望给予指点,90度弯腰~很快我会发布下一个设计模式内容,生命不息,编程不止!
参考书籍:《Head First 设计模式》
参考网站:
Java中的继承与组合:http://www.importnew.com/12907.html
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。