装饰器模式和多线程
在HeadFirst中,有一个StuckBuzz的例子用来阐述装饰器模式,在这篇博文中,打算将同步编程和装饰器模式结合起来介绍一下。
背景
想象一个咖啡后台计费系统,需要计算一杯咖啡的价格。(或者多杯的价格)另外,我们需要饮料的种类和添加的调料。需求要在原材料的价格经常变化的条件下,减少数据的改动。
- 第一种写法
使用继承的模式,类图的如下:
public abstract class Beverage {
String description = "abstract Beverage";
//可以写入配置文件,不计入类属性
final double MILK_PRICE = 2;
final double SOY_PRICE = 1.5;
final double MOCHA_PRICE = 3;
final double WHIP_PRICE = 4;
public Boolean getMilk() {
return milk;
}
public void setMilk(Boolean milk) {
this.milk = milk;
}
public Boolean getSoy() {
return soy;
}
public void setSoy(Boolean soy) {
this.soy = soy;
}
public Boolean getMocha() {
return mocha;
}
public void setMocha(Boolean mocha) {
this.mocha = mocha;
}
public Boolean getWhip() {
return whip;
}
public void setWhip(Boolean whip) {
this.whip = whip;
}
Boolean milk=false; //default, no add
Boolean soy=false; //default, no add
Boolean mocha=false;//default, no add
Boolean whip=false;//default, no add
public String getDescription(){
return description;
}
public double cost() {
double num = 0d;
if (getMilk()) num += MILK_PRICE;
if (getMocha()) num += MOCHA_PRICE;
if (getSoy()) num += SOY_PRICE;
if (getWhip()) num += WHIP_PRICE;
return num;
}
}
class HouseBlend extends Beverage{
String description = "House Blend";
double HOUSE_BLEND_PRICE = 15;
public double cost(){
return HOUSE_BLEND_PRICE + super.cost();
}
}
- 加强需求
调料和饮料单独分类,并响应大家对一种调料加双倍的需求,如何设计这个装饰器,就有很大的学问了。
在这个类图中,最大的特色就是CondimentDecorator是以component的形式并入Beverage中的。因为调料作为附加品,可以加可以不加,也可以加双份。牛奶和抹茶中的Beverage, 记录了当前的饮料的类型,注意哦,这里的饮料可以是加了Milk或者Mocha的咖啡呢,也就实现了加双份调料的功能!
代码如下:
public abstract class Beverage2 {
String description = "abstract Beverage";
public abstract double cost();
public String getDescription(){
return description;
}
}
abstract class CondimentDecorator extends Beverage2{
public abstract String getDescription();
}
//饮料
class Espresso extends Beverage2{
public Espresso() {
description = "Espresso";
}
public double cost(){
return 1.99;
}
}
class Mocha extends CondimentDecorator{
public Mocha(Beverage2 beverage2) {
this.beverage2 = beverage2;
}
Beverage2 beverage2;
public String getDescription(){
return beverage2.getDescription()+ ", Mocha";
}
@Override
public synchronized double cost() {
return .20 + beverage2.cost();
}
}
class Milk extends CondimentDecorator{
public Milk(Beverage2 beverage2) {
this.beverage2 = beverage2;
}
Beverage2 beverage2;
public String getDescription(){
return beverage2.getDescription()+ ", Mocha";
}
@Override
public synchronized double cost() {
return .80 + beverage2.cost();
}
}
Demo如下,注意包装的类,可以重复包装!
//类比于InputStream的装饰器模式
public class DecoratorTest {
public static void main(String[] args) {
//两份Mocha
Beverage2 Espresso = new Espresso();
Mocha mocha = new Mocha(Espresso);
Mocha mocha_double = new Mocha(mocha);
Beverage2 Espresso2 = new Espresso();
Mocha mocha2 = new Mocha(Espresso2);
Milk milk2 = new Milk(mocha2);
System.out.println(mocha_double.getDescription() + " ¥" + mocha_double.cost());
System.out.println(milk2.getDescription() + " ¥" + milk2.cost());
}
}
Demo的显示如下:
与多线程的关系
如果一袋调料可以加入多杯饮料中,为了做一个库存系统,并且同时有3-5人共用这袋调料,那么如何分配这杯饮料呢?如果要在之前的代码的基础上进行重构,要如何做呢?
这个题目应该是开放的,笔者这里使用线程的封闭原则对调料进行控制,一次只能一个人用调料。
//线程封闭
class Condiments {
private final Set<Beverage2> mySet = new HashSet<>();
public synchronized void addBeverage(Beverage2 beverage2){
mySet.add(beverage2);
}
public synchronized boolean containsBeverage(Beverage2 beverage2){
return mySet.contains(beverage2);
}
}
在JAVA中,最好的实现该方法的例子就是Collections.synchronizedXXX(XXX);
XXX可以为List, Map等等。