资讯专栏INFORMATION COLUMN

开发之路(设计模式三:装饰者模式)

Vicky / 1362人阅读

摘要:若要扩展功能,装饰者提供了比继承更有弹性的替代方案。装饰者模式意味着一群装饰者类,这些类用来包装具体组件。装饰者类反映出被装饰组件类型。装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。

嘿嘿嘿,你是不是很喜欢用继承呢?感觉没什么事情是一个“爸爸”类搞不定的,有的话就两个,快来跟我看看这个模式吧,它能让你“断奶”,给爱用继承的人一个全新的设计眼界。

直奔主题,你是否有听说过组合呢?你是否觉得继承往往有的时候没你想象的那么好用呢?如果有,你又是否尝试自己解决这个问题呢?让我们来看个例子~~~~

假设我们有家咖啡店,设计如下
饮料(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() + "	$" + beverage.cost());
        
        //要一杯深焙咖啡        
        Beverage beverage2=new DarkRoast();
        //加两份摩卡
        beverage2=new Mocha(beverage2);
        beverage2=new Mocha(beverage2);
        //加一份奶泡
        beverage2=new Whip(beverage2);
        System.out.println(beverage2.getDescription()+"	$"+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()+"	$"+f1);
    }
}

效果图:

要点:
1:继承属于扩展形式一种,但不见的是达到弹性设计的最佳方式,组合优于继承。
2:应该允许行为可以被拓展,而无需修改现有的代码。
3:装饰者模式意味着一群装饰者类,这些类用来包装具体组件。
4:装饰者类反映出被装饰组件类型。
5:可以使用无数个装饰者包装一个组件。
6:装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。

装饰者模式结束,这个模式是个很有用的模式,可以说是提高代码素养的关键一步,模拟代码已经全部放出,注释也写的比较清楚,若有不理解欢迎留言。

感谢你看到这里,装饰者模式部分结束,本人文笔随便,若有不足或错误之处望给予指点,90度弯腰~很快我会发布下一个设计模式内容,生命不息,编程不止!

参考书籍:《Head First 设计模式》
参考网站:
Java中的继承与组合:http://www.importnew.com/12907.html

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70184.html

相关文章

  • 我的Java设计模式-代理模式

    摘要:下面总结了它俩的异同相同点都需要实现同一个接口或者继承同一个抽象类,并且代理角色和装饰角色都持有被代理角色和构件角色的引用。 写完上一篇之后有小伙伴问我有没有写过代理模式,想看看我的理解。原本我的设计模式系列是按照创建型-行为型-结构型的顺序写下去的,既然小伙伴诚心诚意了,我就大发慈悲的穿插一篇代理模式。开玩笑,题外话。 说起代理模式,就不由得想起经纪人,说起经纪人,就想起了...对,...

    BWrong 评论0 收藏0
  • JS设计模式--装饰模式

    摘要:当接口比较多,装饰器也比较多时,可以独立抽取一个装饰器父类,实现目标类的所有接口,再创建真正的装饰器来继承这个父类。四的实现方式提供了一种类似的注解的语法糖,来实现装饰者模式。 欢迎关注我的公众号睿Talk,获取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 所谓装饰者模式,就是动态的给类或对象增加职责的设计模式。它...

    wmui 评论0 收藏0
  • Java 设计模式装饰模式

    摘要:装饰者模式组成结构抽象构件给出抽象接口或抽象类,以规范准备接收附加功能的对象。装饰者模式图解装饰者模式应用场景需要扩展一个类的功能,或给一个类添加附加职责。装饰者对象接受所有来自客户端的请求。参考资料设计模式 一、了解装饰者模式 1.1 什么是装饰者模式 装饰者模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰者来包裹真实的对...

    kumfo 评论0 收藏0
  • php设计模式

    摘要:我们今天也来做一个万能遥控器设计模式适配器模式将一个类的接口转换成客户希望的另外一个接口。今天要介绍的仍然是创建型设计模式的一种建造者模式。设计模式的理论知识固然重要,但 计算机程序的思维逻辑 (54) - 剖析 Collections - 设计模式 上节我们提到,类 Collections 中大概有两类功能,第一类是对容器接口对象进行操作,第二类是返回一个容器接口对象,上节我们介绍了...

    Dionysus_go 评论0 收藏0

发表评论

0条评论

Vicky

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<