好久没写设计模式了,自从写了两篇之后,就放弃治疗了,主要还是工作太忙了啊(借口,都是借口),过完年以后一直填坑,填了好几个月,总算是稳定下来了,可以打打酱油了。

为什么又重新开始写设计模式呢?学习使我快乐啊(我装逼起来我自己都害怕),其实主要是最近填坑的时候看源代码有点晕,很多代码不知道他们为什么要那么写啊,好气啊

当时第二篇写完,其实就在准备第三篇了,但是,一直也没有写,看了好几遍,但是一直掌握不到精髓(其实现在也掌握不到),感觉挺模糊的,也就一直拖啊拖,拖延症晚期患者已经不用抢救了。。。

先来举个栗子

故事背景:星巴兹咖啡,由于快速扩展,他们现在的订单系统已经跟不上他们的饮料供应需求了,先看一下当前的设计

/**
* 饮料超类
* @author Skysea
*
*/
public abstract class Beverage { protected String description;//描述 public abstract double cost();//消费金额 public Beverage(String description){
this.description = description;
} public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

下面是各种咖啡,这里就只展示一种:

/**
* 首选咖啡
* @author Skysea
*
*/
public class HouseBlend extends Beverage{ public HouseBlend(String description) {
super(description);
} @Override
public double cost() {
return 3.5;
} }

很简单的代码,咖啡继承饮料超类。

啥都不说了,先提需求:

1,购买咖啡时,要求可以在其中加入各种调料,例如:蒸奶,豆浆,摩卡...等等(产品每次给我们说的就是,具体的他们还没定好,能不能做成活的?就是随时可以扩展的那种。知道我为啥要来学设计模式了吗?会被玩死的,真的)

2,各种调料也会参与计价,而且每种调料的价格不一定相同

需求清楚了吧?不清楚?没关系,反正下周上线,不存在的,嘿嘿嘿

先来给出第一版实现(直接扩展超类):

/**
* 饮料超类
* @author Skysea
*
*/
public abstract class Beverage { protected String description;//描述 public abstract double cost();//消费金额 private boolean milk;//牛奶 private boolean soy;//豆浆 private boolean mocha;//摩卡 private boolean whip;//奶泡 //省略get set方法...
}

不就是下周上线吗?不就是要扩展吗?要多少,就在超类里面加就好了啊,然后再对每一种饮料进行处理,像这样:

/**
* 首选咖啡
* @author Skysea
*
*/
public class HouseBlend extends Beverage{ public HouseBlend(String description) {
super(description);
} @Override
public double cost() { double cost = 3.5;
if(hasMilk()){
cost += 0.5;
} if(hasMocha()){
cost += 0.6;
} if(hasSoy()){
cost += 0.3;
} if(hasWhip()){
cost += 0.4;
} return cost;
} }

感觉每一种都要写这么多if好麻烦啊,不存在的,这种东西,抽出来嘛:

/**
* 饮料超类
* @author Skysea
*
*/
public abstract class Beverage { /**
* 调料消费
* @return
*/
protected double flavourCost(){
double cost = 0.0;
if(hasMilk()){
cost += 0.5;
}
if(hasMocha()){
cost += 0.6;
}
if(hasSoy()){
cost += 0.3;
}
if(hasWhip()){
cost += 0.4;
}
return cost;
}
//...
}

子类:

/**
* 首选咖啡
* @author Skysea
*
*/
public class HouseBlend extends Beverage{ public HouseBlend(String description) {
super(description);
} @Override
public double cost() {
return 3.5 + flavourCost();
} }

感觉也挺好的啊,每次要添加的时候,先去超类添加一个属性,然后在超类的 flavourCost()方法中,添加一段代码,以前的子类完全不用动,逻辑也是妥妥的,一切都是很OK的

但是,这样写至少有三处是不符合逻辑的:

1,所有的子类的cost方法都必须要加上一句 xxx + flavourCost(),不觉得写的次数太多了吗?一般一个方法写N次,那大部分最后会变成坑

2,超类的所有属性并不是每一个子类都能用上的

3,违背了开闭原则,每一次扩展虽然不用修改子类,但是却会去修改父类的属性,以及父类的flavourCost()方法

在现在的这个需求下,这些不合理都是体现不出来有什么问题的,但是,代码中不符合逻辑的东西早晚有一天会对整个模块的设计造成巨大的影响,两种情况除外:

1,你不干了(钱没给够或者我不开心毕竟程序员都是非常任性的),对模块的影响跟你没有半毛钱的关系

2,这个模块不扩展,不维护,或者扩展、维护还没有达到临界点(时间根据前期逻辑混乱程度成反比)

别问我怎么知道的,因为我TM还没走,所以就来学习来了,我擦,跑题了。。。

下面来聊聊刚学的正确姿势:

装饰者模式

先说超类 Beverage(不变,保持最最原始的样子):

/**
* 饮料超类
* @author Skysea
*
*/
public abstract class Beverage { protected String description;//描述 public abstract double cost();//消费金额 public Beverage(String description){
this.description = description;
} public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

为什么不变?

要回答这个问题,首先要考虑,整个模块中,到底是什么东西一直变来变去?没错,就是调料啊。

一般变化的怎么处理?

抽离出来,把它和稳定的东西抽离出来,即保证了它的扩展性,又提高了代码的稳定性,遵循了开闭原则

所以为什么不变?1,它很稳定。2,它很稳定。没有3,我要逼死强迫症

接下来,贴一下它的子类代码,调整的地方:

    public HouseBlend(String description) {
super(description);
}

把 description放在构造函数内写死,为啥?你创建一个咖啡叫啥名字,难道还要客户端帮你想好?

让他去做也是可以的,但是有两个问题你要想清楚:

1,他知道你住哪里吗?

2,你走夜路吗?

卧槽,走远了走远了...

继续:

/**
* 首选咖啡
* @author Skysea
*
*/
public class HouseBlend extends Beverage{ public HouseBlend() {
super("首选咖啡");
}
@Override
public double cost() {
return 3.5;
}
}

为了保证最后的测试类可以正常的跑,我把其他的也写一些出来...

/**
* 浓咖啡
* @author Skysea
*
*/
public class Espresso extends Beverage { public Espresso() {
super("浓咖啡");
} @Override
public double cost() {
return 2.5;
} }
/**
* 焦炒咖啡
* @author Skysea
*
*/
public class DarkRoast extends Beverage{ public DarkRoast() {
super("焦炒咖啡");
} @Override
public double cost() {
return 3.2;
}
}

然后来到最重要的装饰超类CondimentDecorator (第一版):

/**
* 调料装饰类
* @author Skysea
*
*/
public abstract class CondimentDecorator extends Beverage { protected Beverage beverage; public CondimentDecorator(String description) {
super(description);
} //重写cost方法
@Override
public String getDescription(){
return this.beverage.getDescription() + ", " +this.description;
} //直接实现cost方法
public double cost(){
return this.beverage.cost() + condimentCost();
} protected abstract double condimentCost();//重新抽象一个调料的价格方法 }

装饰子类(Mocha ):

/**
* 调料:摩卡
* @author Skysea
*
*/
public class Mocha extends CondimentDecorator {
public Mocha() {
super("摩卡");
}
@Override
protected double condimentCost() {
return 0.6;
}
}

是不是感觉很简单?所有的逻辑都在装饰超类里面处理好了,感觉妥妥的,没什么不对,而且其他的子类实现起来也是非常的轻松

上面的装饰类是我自己的第一版实现,写完之后,我发现,这个东西,和我们最初想要的装饰类有很大的区别:

不灵活

现在我们来看看装饰者模式的定义:

装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。(百度百科)

想要应对越复杂的变化,那么就要给子类赋予越大的权限,让它接触越多的东西,只有这样才能做灵活,当然,灵活也是相对的,这个也是必须在实际项目中取舍

所以,上面的装饰类实现,根本无法满足当前需求所需要的灵活性,比如:使用摩卡的时候,所有的饮料满20减5块怎么玩?是不是感觉玩不动了?

问题在哪里呢?

    //直接实现cost方法
public double cost(){
return this.beverage.cost() + condimentCost();
}

就是在这里,这个地方把cost方法定的太死板了,也不能说这样不对,只是,这样所有继承CondimentDecorator的其实只能算装饰者模式的特例,要实现这样的东西,就必须在CondimentDecorator与Beverage中间再抽象一层。

就像这样CondimentDecorator extends Decorator,Decorator extends Beverage,其他的调料继承 Decorator (咳,别听我乱BB)

讲讲正确的装饰类及实现吧:

/**
* 调料装饰类
* @author Skysea
*
*/
public abstract class CondimentDecorator extends Beverage { protected Beverage beverage; public CondimentDecorator(Beverage beverage, String description){
super(description);
this.beverage = beverage;
} //根据情况来觉得是不是要抽象出来让子类实现,原因和刚才抽象condimentCost()一样,分情况,没有什么是对不对的
@Override
public String getDescription(){
return this.beverage.getDescription() + ", " +this.description;
} }

调料实现类:

/**
* 调料:牛奶
* @author Skysea
*
*/
public class Milk extends CondimentDecorator{ public Milk(Beverage beverage) {
super(beverage, "牛奶");
} @Override
public double cost() {
return 0.5 + beverage.cost();
} }
/**
* 调料:摩卡
* @author Skysea
*
*/
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
super(beverage, "摩卡");
} @Override
public double cost() {
return 0.6 + beverage.cost();
} }
/**
* 调料:豆浆
* @author Skysea
*
*/
public class Soy extends CondimentDecorator{ public Soy(Beverage beverage) {
super(beverage, "豆浆");
} @Override
public double cost() {
return 0.3 + beverage.cost();
} }

....

测试类:

/**
* 装饰模式测试类
* @author Skysea
*
*/
public class Test {
public static void main(String[] args) {
//初始化浓咖啡
Beverage beverage = new Espresso();//$2.5
System.out.println(beverage.getDescription() + " $"+ beverage.cost()); //初始化焦炒咖啡
Beverage beverage2 = new DarkRoast();//$3.2
//用调料来装饰它
beverage2 = new Mocha(beverage2);//$0.6
beverage2 = new Mocha(beverage2);//$0.6
beverage2 = new Soy(beverage2);//$0.3 System.out.println(beverage2.getDescription() + " $"+ beverage2.cost()); //初始化焦炒咖啡
Beverage beverage3 = new HouseBlend();//3.5
beverage3 = new Mocha(beverage3);//$0.6
beverage3 = new Milk(beverage3);//$0.5
beverage3 = new Soy(beverage3);//$0.3 System.out.println(beverage3.getDescription() + " $"+ beverage3.cost());
}
}

运行结果:

为了证明我没有随便拿个假截图来骗你们,我特意把价格都标到测试类后面的

为啥后面那么长一串?这肯定不是我的锅啊,double的锅,推荐用BigDecimal来玩这个,当然也可以用long型,或者int都是可以的,实际项目也是这样

用什么主要看:

1,精度要求高不高

2,速度要求快不快

3,看产品会不会让你保留小数点2位和保留小数点3位换着来玩的情况(都是泪,不说了..)

headfirst设计模式(3)—装饰者模式的更多相关文章

  1. HeadFirst设计模式之装饰者模式

    一. 1.The Decorator Pattern attaches additional responsibilities to an object dynamically.Decorators ...

  2. Java 设计模式泛谈&装饰者模式和单例模式

    设计模式(Design Pattern) 1.是一套被反复使用.多人知晓的,经过分类编目 的 代码设计经验总结.使用设计模式是为了可重用代码,让代码更容易维护以及扩展. 2.简单的讲:所谓模式就是得到 ...

  3. C#设计模式(9)——装饰者模式(Decorator Pattern)

    一.引言 在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).A ...

  4. 设计模式之装饰者模式-java实例

    设计模式之装饰者模式 需求场景 我们有了别人提供的产品,但是别人提供的产品对我们来说还不够完善,我们需要对这个产品的功能进行补强,此时可以考虑使用装饰者模式. 我们已经有了产品,而且这个产品的功能非常 ...

  5. Java设计模式 - - 单例模式 装饰者模式

    Java设计模式 单例模式 装饰者模式 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 静态代理模式:https://www.cnblogs.com/StanleyBlogs/p/1 ...

  6. python 设计模式之装饰器模式 Decorator Pattern

    #写在前面 已经有一个礼拜多没写博客了,因为沉醉在了<妙味>这部小说里,里面讲的是一个厨师苏秒的故事.现实中大部分人不会有她的天分.我喜欢她的性格:总是想着去解决问题,好像从来没有怨天尤人 ...

  7. PHP设计模式之装饰器模式(Decorator)

    PHP设计模式之装饰器模式(Decorator) 装饰器模式 装饰器模式允许我们给一个类添加新的功能,而不改变其原有的结构.这种类型的类属于结构类,它是作为现有的类的一个包装 装饰器模式的应用场景 当 ...

  8. 实践GoF的23种设计模式:装饰者模式

    摘要:装饰者模式通过组合的方式,提供了能够动态地给对象/模块扩展新功能的能力.理论上,只要没有限制,它可以一直把功能叠加下去,具有很高的灵活性. 本文分享自华为云社区<[Go实现]实践GoF的2 ...

  9. Java 的设计模式之一装饰者模式

    刚开始接触装饰者的设计模式,感觉挺难理解的,不够后来花了一个晚上的时间,终于有头绪了 装饰者设计模式:如果想对已经存在的对象进行装饰,那么就定义一个类,在类中对已经有的对象进行功能的增强或添加另外的行 ...

  10. Head First 设计模式 --3 装饰者模式 开闭原则

    装饰者模式:动态的将责任附加到对象上,若要扩展功能,装饰者提供了比集成更有弹性的替代方案.设计原则:1:封装变化2:多用组合,少用继承3:针对接口编程,不针对实现编程4:为对象之间的松耦合设计而努力5 ...

随机推荐

  1. 用UE4来做Zego即构的房间列表

    Zego即构是一家做直播的服务商,Zego即构自己的房间列表,本文只是测试功能用,相应代码并没完全测试,请选择性参考. 我们在UE4中来实现一下,我感觉这个过程有点意思,UE4中C++与蓝图和UI的互 ...

  2. hdu 6125 -- Free from square(状态压缩+分组背包)

    题目链接 Problem Description There is a set including all positive integers that are not more then n. Ha ...

  3. asp.net core MVC 过滤器之ActionFilter过滤器(二)

    本系类将会讲解asp.net core MVC中的内置全局过滤器的使用,将分为以下章节 asp.net core MVC 过滤器之ExceptionFilter过滤器(一) asp.net core ...

  4. js 日期大小比较

    <!DOCTYPE HTML><html><body><script>//获取起始日期 //转换为日期格式var startDate='2016-06- ...

  5. 开源的 Restful Api 集成测试工具 Hitchhiker

    Hitchhiker 是一款开源的 Restful Api 集成测试工具,你可以在轻松部署到本地,和你的team成员一起管理Api. 先上图看看: 简单介绍 背景是Team在开发一些Api,这些Api ...

  6. 8.23.4 IO-输入输出16个流

    字节流: FileInputStream FileInputStream fIn = new FileInputStream("1.avi");   FileOutputStrea ...

  7. WeQuant交易策略—5日均线

    简单的价格突破策略.当前价格超过最近5个收盘价的均价,则全仓买入:低于均价,则全仓卖出 代码 # 简单的价格突破策略.当前价格超过最近5个收盘价的均价,则全仓买入:低于均价,则全仓卖出 # PARAM ...

  8. 简单利用HTTP中的PUT协议拿下SHELL

    第一次用方法拿shell,之前遇到的都是没有写入权限的. 站太辣鸡,纯粹练手,就不打码了. 此次实战会用到的HTTP请求方法: OPTIONS,PUT,MOVE/COPPY * 战前准备 0x01 什 ...

  9. 【渗透课程】第四篇-Web安全之信息探测

    Web之信息探测,从这篇开始就正式进入了Web渗透实战过程了,嗯,前面都是讲基础,下面我们来讲Web中的信息探测. 信息探测,主要的目的 收集目标服务器系统信息(IP,服务器所用系统等) 收集目标网站 ...

  10. 转每天一个linux命令(3):pwd命令

    Linux中用 pwd 命令来查看"当前工作目录"的完整路径. 简单得说,每当你在终端进行操作时,你都会有一个当前工作目录. 在不太确定当前位置时,就会使用pwd来判定当前目录在文 ...