设计模式-7.装饰者模式

1.装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

2.作用:动态地给一个对象添加一些额外的职责,装饰者提供了比继承更有弹性的替代方案。

3.我觉得我写的不会比这个更好了:https://www.runoob.com/design-pattern/decorator-pattern.html

 

一、星巴兹咖啡的故事

  我们通过一个生动有趣的例子来引出我们今天的主角--装饰者模式。

  1、现在呢,有一个咖啡馆,它有一套自己的订单系统,当顾客来咖啡馆的时候,可以通过订单系统来点自己想要的咖啡。他们原先的设计是这样子的:cost()计算价格

 

2、此时、咖啡馆为了吸引更多的顾客,需要在订单系统中允许顾客选择加入不同调料的咖啡,例如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。

  下面是他们的第一次尝试:

 

 

   这种设计肯定是不行的,简直分分钟把人逼疯的节奏,有木有!

  3、这时,有个人提出了新的方案,利用实例变量和继承,来追踪这些调料。

    具体为:先从Beverage基类下手,加上实例变量代表是否加上调料(牛奶、豆浆、摩卡、奶泡……),

 

 

 

 

这种设计虽然满足了现在的需求,但是我们想一下,如果出现下面情况,我们怎么办,

    ①、调料价钱的改变会使我们更改现有代码。

    ②、一旦出现新的调料,我们就需要加上新的方法,并改变超类中的cost()方法。

    ③、以后可能会开发出新饮料。对这些饮料而言(例如:冰茶),某些调料可能并不适合,但是在这个设计方式中,Tea(茶)子类仍将继承那些不适合的方法,例如:hasWhip()(加奶泡)。

    ④、万一顾客想要双倍摩卡咖啡,怎么办?

  很明显,上面的设计并不能够从根本上解决我们所碰到的问题。并且这种设计违反了 开放关闭原则(类应该对扩展开放,对修改关闭。)。

  那我们怎么办呢?好啦,装饰者可以非常完美的解决以上的所有问题,让我们有一个设计非常nice的咖啡馆。

 

 

二、定义

  装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

三、实现

  下面我们就利用装饰者模式来实现一个全新的咖啡馆。

  1、我们要以饮料为主体,然后在运行时以调料来“装饰”(decorate)饮料。比方说,如果顾客想要摩卡和奶泡深焙咖啡,那么,要做的是:

    ①、拿一个深焙咖啡(DarkRoast)对象

    ②、以摩卡(Mocha)对象装饰它

    ③、以奶泡(Whip)对象装饰它

    ④、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。

  好了!但是如何“装饰”一个对象,而“委托”又要如何与此搭配使用呢?那就是把装饰者对象当成“包装者”。让我们看看这是如何工作的:

 

 

 

 

 

2、设计

  我们将我们所知道的写下来:

    ①、装饰者和被装饰对象有相同的超类型。

    ②、你可以用一个或多个装饰者包装一个对象。

    ③、既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。

    ④、装饰者可以在所委托被装饰者的行为之前与 / 或之后,加上自己的行为,以达到特定的目的。

    ⑤、对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。

  下面,我们来看一下装饰者模式的类图:

 

   利用装饰者模式来实现我们的订单系统的类图:

 

 

我们已经将订单系统设计完毕,下面让我们用代码来实现它吧!

  3、代码实现:

饮料抽象类:

package com.newland.draw.decorator1;

import java.math.BigDecimal;

/**
 * 饮料抽象类,使用接口也可以
 * @author lx
 *
 */
public abstract class Beverage {

	String description = "Unknown Beverage";
	
	public String getDescription() {
        return description;
    }
	/**
	 * 计算饮料价格
	 * @return
	 */
	public abstract BigDecimal cost();
	
}

具体的饮料类:深焙咖啡类

package com.newland.draw.decorator1;

import java.math.BigDecimal;

/**
 * 深焙咖啡类
 * @author lx
 *
 */
public class DarkRoast extends Beverage{

	/**
              * 说明他是DarkRoast饮料(一种具体饮料)
     */
    public DarkRoast() {
        description = "DarkRoast";
    }
	
    //返回DarkRoast(深焙咖啡)的价格
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return new BigDecimal("3.00");
	}

}

具体的饮料类:低咖啡因类咖啡

package com.newland.draw.decorator1;

import java.math.BigDecimal;

/**
 * 低咖啡因类咖啡(一种具体饮料)
 * @author lx
 *
 */
public class Decaf extends Beverage{

	/**
              * 说明他是Decaf饮料
     */
    public Decaf() {
        description = "Decaf";
    }
	
    //返回Decaf(低咖啡因类咖啡)的价格
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return new BigDecimal("4.00");
	}

}

具体的饮料类:浓缩类咖啡

package com.newland.draw.decorator1;

import java.math.BigDecimal;

/**
 * 浓缩类咖啡(一种具体饮料)
 * @author lx
 *
 */
public class Espresso extends Beverage{

	/**
              * 说明他是Espresso饮料
     */
    public Espresso() {
        description = "Espresso";
    }
	
    //返回Decaf(浓缩类咖啡)的价格
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return new BigDecimal("2.00");
	}

}

以上是被装饰的类

 

调料装饰者抽象类

package com.newland.draw.decorator1;

public abstract class CondimentDecorator extends Beverage{

	
	 /**
	  * 所有的调料装饰者都必须重新实现getDescription()方法
	  * 这样才能够用递归的方式来得到所选饮料的整体描述
     *
     * @return
     */
    public abstract String getDescription();
    
}

摩卡调料类:

package com.newland.draw.decorator1;

import java.math.BigDecimal;
/**
 * 摩卡调料类
 * @author lx
 *
 */
public class Mocha extends CondimentDecorator{
   //用一个实例变量记录饮料,也就是被装饰者
	Beverage beverage;
    
    public Mocha(Beverage beverage) {
    	this.beverage = beverage; 
    }
    
    //在原来饮料的基础上添加上Mocha描述(原来的饮料加入Mocha调料,被Mocha调料装饰)
	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return beverage.getDescription()+",Mocha";
	}
	
	//在原来饮料的基础上加上Mocha的价格(原来的饮料加入Mocha调料,被Mocha调料装饰)
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return new BigDecimal("0.2").add(beverage.cost());
	}

}

豆浆调料类:

package com.newland.draw.decorator1;

import java.math.BigDecimal;
/**
 * 豆浆调料类
 * @author lx
 *
 */
public class Soy extends CondimentDecorator{
   //用一个实例变量记录饮料,也就是被装饰者
	Beverage beverage;
    
    public Soy(Beverage beverage) {
    	this.beverage = beverage; 
    }
    
    //在原来饮料的基础上添加上Soy描述(原来的饮料加入Soy调料,被Soy调料装饰)
	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return beverage.getDescription()+",Soy";
	}
	
	//在原来饮料的基础上加上Soy的价格(原来的饮料加入Soy调料,被Soy调料装饰)
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return new BigDecimal("0.3").add(beverage.cost());
	}

}

奶泡调料类: 

package com.newland.draw.decorator1;

import java.math.BigDecimal;
/**
 * 奶泡调料类
 * @author lx
 *
 */
public class Whip extends CondimentDecorator{
   //用一个实例变量记录饮料,也就是被装饰者
	Beverage beverage;
    
    public Whip(Beverage beverage) {
    	this.beverage = beverage; 
    }
    
    //在原来饮料的基础上添加上Whip描述(原来的饮料加入Whip调料,被Whip调料装饰)
	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return beverage.getDescription()+",Whip";
	}
	
	//在原来饮料的基础上加上Whip的价格(原来的饮料加入Whip调料,被Whip调料装饰)
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return new BigDecimal("0.4").add(beverage.cost());
	}

}

咖啡馆(模拟顾客下单):

package com.newland.draw.decorator1;
/**
 * 咖啡馆模拟顾客下单
 * @author lx
 *
 */
public class StarbuzzCoffee {

	public static void main(String[] args) {
	        //订一杯Espresso(2.00),不需要调料,打印出它的描述与价钱。
	        Beverage beverage = new Espresso();
	        System.out.println("Description: " + beverage.getDescription() + " $" + beverage.cost());

	        //制造出一个DarkRoast(3.00)对象,用Mocha(0.2)装饰它,用第二个Mocha(0.2)装饰它,用Whip(0.4)装饰它,打印出它的描述与价钱。
	        Beverage beverage2 = new DarkRoast();
	        beverage2 = new Mocha(beverage2);
	        beverage2 = new Mocha(beverage2);
	        beverage2 = new Whip(beverage2);
	        System.out.println("Description: " + beverage2.getDescription() + " $" + beverage2.cost());

	        //再来一杯调料为豆浆(Soy 0.3)、摩卡(Mocha 0.2)、奶泡(Whip 0.4)的Decaf(低咖啡因咖啡 4.00),打印出它的描述与价钱。
	        Beverage beverage3 = new Decaf();
	        beverage3 = new Soy(beverage3);
	        beverage3 = new Mocha(beverage3);
	        beverage3 = new Whip(beverage3);
	        System.out.println("Description: " + beverage3.getDescription() + " $" + beverage3.cost());
	    }
}

运行结果:

从以上,我们可以知道,当我们使用继承,导致子类膨胀,我们不想增加很多子类的情况下,将具体功能职责划分,同时继承装饰者超类,动态地给一个对象添加一些额外的职责便实现了我们的装饰者模式。

四、优缺点

  1、优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

  2、缺点:多层装饰比较复杂。

五、使用场景: 

  1、扩展一个类的功能。

   2、动态增加功能,动态撤销。

  实际使用:这里我们说一下,在java中I/O便使用了装饰者模式。

六、装饰者用到的设计原则:

  1、多用组合,少用继承。

  2、对扩展开放,对修改关闭。

七 上面是原作者的内容。我将代码改为接口的形式

饮料接口类,这里我把描述也作为一个接口方法实现

package com.newland.draw.decorator4;

import java.math.BigDecimal;

/**
 * 饮料接口类
 * @author lx
 *
 */
public interface Beverage {

	
	public  String getDescription();
	/**
	 * 计算饮料价格
	 * @return
	 */
	public  BigDecimal cost();
	
}

深焙咖啡类

package com.newland.draw.decorator4;

import java.math.BigDecimal;

/**
 * 深焙咖啡类
 * @author lx
 *
 */
public class DarkRoast implements Beverage{

	/**
              * 说明他是DarkRoast饮料(一种具体饮料)
     */
	
    //返回DarkRoast(深焙咖啡)的价格
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return new BigDecimal("3.00");
	}

	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return "DarkRoast";
	}

}

低咖啡因类咖啡类

package com.newland.draw.decorator4;

import java.math.BigDecimal;

/**
 * 低咖啡因类咖啡(一种具体饮料)
 * @author lx
 *
 */
public class Decaf implements Beverage{

	/**
              * 说明他是Decaf饮料
     */
	
    //返回Decaf(低咖啡因类咖啡)的价格
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return new BigDecimal("4.00");
	}

	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return "Decaf";
	}

}

浓缩类咖啡类

package com.newland.draw.decorator4;

import java.math.BigDecimal;

/**
 * 浓缩类咖啡(一种具体饮料)
 * @author lx
 *
 */
public class Espresso implements Beverage{

	/**
              * 说明他是Espresso饮料
     */
	
    //返回Decaf(浓缩类咖啡)的价格
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return new BigDecimal("2.00");
	}

	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return "Espresso";
	}

}

所有调料类的父类

package com.newland.draw.decorator4;

import java.math.BigDecimal;

public class CondimentDecorator implements Beverage{

	Beverage beverage;
	 /**
	  * 所有调料类继承此类
	  *
     *
     * @return
     */
	public  CondimentDecorator(Beverage beverage) {
	   this.beverage = beverage;
   }
	
	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return beverage.getDescription();
	}
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return beverage.cost();
	}
    
}

装饰者类: 摩卡,豆浆,奶泡 调料类

package com.newland.draw.decorator4;

import java.math.BigDecimal;
/**
 * 摩卡调料类
 * @author lx
 *
 */
public class Mocha extends CondimentDecorator{
 

	public Mocha(Beverage beverage) {
		super(beverage);
		// TODO Auto-generated constructor stub
	}

	//在原来饮料的基础上添加上Mocha描述(原来的饮料加入Mocha调料,被Mocha调料装饰)
	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return super.getDescription()+",Mocha";
	}
	
	//在原来饮料的基础上加上Mocha的价格(原来的饮料加入Mocha调料,被Mocha调料装饰)
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return new BigDecimal("0.2").add(super.cost());
	}

}

 

package com.newland.draw.decorator4;

import java.math.BigDecimal;
/**
 * 豆浆调料类
 * @author lx
 *
 */
public class Soy extends CondimentDecorator{
   //用一个实例变量记录饮料,也就是被装饰者
	

	public Soy(Beverage beverage) {
		super(beverage);
		// TODO Auto-generated constructor stub
	}

	//在原来饮料的基础上添加上Soy描述(原来的饮料加入Soy调料,被Soy调料装饰)
	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return beverage.getDescription()+",Soy";
	}
	
	//在原来饮料的基础上加上Soy的价格(原来的饮料加入Soy调料,被Soy调料装饰)
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return new BigDecimal("0.3").add(beverage.cost());
	}

}
package com.newland.draw.decorator4;

import java.math.BigDecimal;
/**
 * 奶泡调料类
 * @author lx
 *
 */
public class Whip extends CondimentDecorator{
  
	

    public Whip(Beverage beverage) {
		super(beverage);
		// TODO Auto-generated constructor stub
	}

	//在原来饮料的基础上添加上Whip描述(原来的饮料加入Whip调料,被Whip调料装饰)
	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return beverage.getDescription()+",Whip";
	}
	
	//在原来饮料的基础上加上Whip的价格(原来的饮料加入Whip调料,被Whip调料装饰)
	@Override
	public BigDecimal cost() {
		// TODO Auto-generated method stub
		return new BigDecimal("0.4").add(beverage.cost());
	}

}

咖啡馆类 模拟顾客下单:

package com.newland.draw.decorator4;
/**
 * 咖啡馆模拟顾客下单
 * @author lx
 *
 */
public class StarbuzzCoffee {

	public static void main(String[] args) {
	        //订一杯Espresso(2.00),不需要调料,打印出它的描述与价钱。
	        Beverage beverage = new Espresso();
	        System.out.println("Description: " + beverage.getDescription() + " $" + beverage.cost());

	        //制造出一个DarkRoast(3.00)对象,用Mocha(0.2)装饰它,用第二个Mocha(0.2)装饰它,用Whip(0.4)装饰它,打印出它的描述与价钱。
	        Beverage beverage2 = new DarkRoast();
	        Mocha mocha = new Mocha(beverage2);
	        Mocha mocha1 = new Mocha(mocha);
	        Whip whip = new Whip(mocha1);
	        System.out.println("Description: " + whip.getDescription() + " $" + whip.cost());

	        //再来一杯调料为豆浆(Soy 0.3)、摩卡(Mocha 0.2)、奶泡(Whip 0.4)的Decaf(低咖啡因咖啡 4.00),打印出它的描述与价钱。
	        Beverage beverage3 = new Decaf();
	        beverage3 = new Soy(beverage3);
	        beverage3 = new Mocha(beverage3);
	        beverage3 = new Whip(beverage3);
	        System.out.println("Description: " + beverage3.getDescription() + " $" + beverage3.cost());
	    }
}

运行结果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值