一、定义
关于装饰者模式的定义,设计模式书中肯定都有,我就直接引用了:
装饰者模式动态低将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。 –摘自《Head First 设计模式》
其实装饰者模式的重点在于给对象动态的附加职责,通过对象组合的方式,运行时装饰对象,在不改变任何底层代码的情况下,给现有对象赋予新的职责。现在不清楚没关系,我们接下来慢慢探究,当看完这篇文章之后,一定要记得回过头来,好好琢磨一下定义哦。
二、需求
现在我们要为卖煎饼的大妈设计一套系统,让大妈能更好的算账收钱。大妈主要经营煎饼(7元),烤冷面(6元)两种小吃,并可以往其中添加配料烤肠(1元),鸡蛋(1元),鸡里脊肉(3元),酥脆(1元,酥脆为煎饼独有),如果生意红火,可能会考虑扩大经营其他小吃或添加配料种类。现需要设计出一套系统,以便快速计算出每位顾客所购小吃的价格。
这是一个小吃类接口:
/**
* 这是小吃的接口
*/
public interface ISnack {
//获得小吃的描述
String getDescription();
//获得小吃的价格
double cost();
}
符合针对接口编程,不针对实现编程的OO原则
这是一个配料类的接口,配料类接口IDecoratorSnack继承自ISnack接口,因为需要两接口的实现类(装饰者与被装饰对象)具有相同的超类型,只有这样IDecoratorSnack接口才能取代ISnack接口。
/**
* 这个是小吃配料的接口
*/
public interface IDecoratorSnack extends ISnack{
//除了配料的描述和价格,你完全可以根据自己的需要编写配料方法,例如大份小份:
//只需要定义setSize()和getSize()方法
}
这是小吃和配料价格的枚举类,将价格统一写在这里,即容易修改,也方便自己或他人查阅。
/**
* 这是小吃和配料的价格枚举,当价格改变的时候,可以轻易改变
* @author zmj
*/
public enum Price {
//鸡蛋、烤肠、鸡里脊肉、薄脆、烤冷面、煎饼
Egg(1.0), Sausage(1.0), Chicken(3.0), Crackers(1.0), ColdRoastSnake(6.0), PancakeSnack(7.0);
double price;
private Price(double price) {
this.price = price;
}
}
这是具体的煎饼小吃
/**
* 这个是煎饼类
* @author zmj
*/
public class PancakeSnack implements ISnack{
@Override
public String getDescription() {
return "煎饼";
}
@Override
public double cost() {
return Price.PancakeSnack.price;
}
}
小吃类(被装饰者)既可以单独使用,也可以被配料类(装饰者)包着使用,因为装饰者和被装饰者对象具有相同的超类型,所以在任何需要原始对象(被包装的)的场合,都可以用装饰过的对象替代它
这是具体的烤冷面小吃
/**
* 这是烤冷面类
*/
public class ColdRoastSnake implements ISnack{
@Override
public String getDescription() {
return "烤冷面";
}
@Override
public double cost() {
return Price.ColdRoastSnake.price;
}
}
这是具体的鸡蛋配料
/**
* 这个是配料鸡蛋类
*/
public class Egg implements IDecoratorSnack{
//持有被修饰的对象
ISnack iSnack;
public Egg(ISnack iSnack) {
this.iSnack = iSnack;
}
@Override
public String getDescription() {
return iSnack.getDescription() + " 添加鸡蛋";
}
@Override
public double cost() {
return iSnack.cost() + Price.Egg.price;
}
}
每个装饰者都持有一个被装饰者的对象,这个被装饰者对象不一定是原始对象,也可能是被包装了多层的对象。通过这种组合,加入了新的行为。符合对扩展开放,对修改关闭的OO原则
这是具体的烤肠配料
/**
* 这个是配料烤肠类
*/
public class Sausage implements IDecoratorSnack{
//持有被修饰对象的引用
ISnack iSnack;
public Sausage(ISnack iSnack) {
this.iSnack = iSnack;
}
@Override
public String getDescription() {
return iSnack.getDescription() + " 添加香肠";
}
@Override
public double cost() {
//被修饰对象的价格+烤肠价格
return iSnack.cost() + Price.Sausage.price;
}
}
这是具体的鸡里脊肉配料
/**
* 这是配料鸡肉类
*/
public class Chicken implements IDecoratorSnack{
ISnack iSnack;
public Chicken(ISnack iSnack) {
this.iSnack = iSnack;
}
@Override
public String getDescription() {
return iSnack.getDescription() + " 添加鸡里脊肉";
}
@Override
public double cost() {
return iSnack.cost() + Price.Chicken.price;
}
}
这是具体的薄脆配料
/**
* 这是配料薄脆类
*/
public class Crackers implements IDecoratorSnack{
ISnack iSnack;
public Crackers(ISnack iSnack) {
this.iSnack = iSnack;
}
@Override
public String getDescription() {
return iSnack.getDescription() + " 添加薄脆";
}
@Override
public double cost() {
return iSnack.cost() + Price.Crackers.price;
}
}
利用装饰者模式,常常会造成设计中产生大量的小类,如果过度使用,会使程序变得很复杂。另外可能还会出现类型问题,如果把代码写成依赖于具体的被装饰者类型,不针对抽象接口进行编程,那么就会出现问题。
测试一下我们的这个设计吧
public class DecoratorPatternTest {
public static void main(String[] ags) {
//一位顾客要了一个烤冷面,加了一个肠
ISnack snackTwo = new ColdRoastSnake();
snackTwo = new Sausage(snackTwo);
System.out.println(snackTwo.getDescription() + "价格:" + snackTwo.cost() + "元");
//一位顾客要了一个煎饼,加了两个鸡蛋,一个肠,一个鸡里脊肉
ISnack snackOneISnack = new PancakeSnack();
snackOneISnack = new Egg(snackOneISnack);
snackOneISnack = new Egg(snackOneISnack);
snackOneISnack = new Sausage(snackOneISnack);
snackOneISnack = new Chicken(snackOneISnack);
System.out.println(snackOneISnack.getDescription() + "价格:" + snackOneISnack.cost() + "元");
}
}
最终的运行结果