设计模式之装饰器模式

1、场景推导

现在有一个业务场景,星巴克卖咖啡
一开始只有四种咖啡,分别是:

  • Decaf无卡咖啡
  • Espresso浓缩咖啡
  • DrakRoast焦糖咖啡
  • HouseBlend混合咖啡

每种咖啡,都有描述,都有价格。 这是所有咖啡的共性,既然是共性,就要上提到父类Beverage,饮料类中。
在这里插入图片描述

@Data
abstract class Beverage {
    private String description;

    public Beverage(String description) {
        this.description = description;
    }

    // 花费
    public abstract double cost();
}

class Decaf extends Beverage {

    public Decaf() {
        super("无卡咖啡");
    }

    @Override
    public double cost() {
        return 1;
    }
}

class Espresso extends Beverage {

    public Espresso() {
        super("浓缩咖啡");
    }

    @Override
    public double cost() {
        return 5;
    }
}

class DrakRoast extends Beverage {

    public DrakRoast() {
        super("焦糖咖啡");
    }

    @Override
    public double cost() {
        return 15;
    }
}

class HouseBlend extends Beverage {

    public HouseBlend() {
        super("混合咖啡");
    }

    @Override
    public double cost() {
        return 10;
    }
}

-------------------------------------------------时空线-------------------------------------

public class AppTest {
    public static void main(String[] args) {
        Beverage decaf = new Decaf();
        Beverage espresso = new Espresso();
        Beverage drakRoast = new DrakRoast();
        Beverage houseBlend = new HouseBlend();
        System.out.println(String.format("咖啡描述:%s,咖啡价格:%.2f", decaf.getDescription(), decaf.cost()));
        System.out.println(String.format("咖啡描述:%s,咖啡价格:%.2f", espresso.getDescription(), espresso.cost()));
        System.out.println(String.format("咖啡描述:%s,咖啡价格:%.2f", drakRoast.getDescription(), drakRoast.cost()));
        System.out.println(String.format("咖啡描述:%s,咖啡价格:%.2f", houseBlend.getDescription(), houseBlend.cost()));
    }
}

这样看,代码好像并没有什么问题

可是现在变化来了,星巴克老板为了提高竞争力,需要往咖啡里面加调料

调料有

  • 摩卡mocha
  • 泡沫bubble
  • 豆浆soy
  • 牛奶milk

我们现在想要给上面写的四个类加上牛奶,但是由于我们不能修改源代码并且符合开闭原则,我们只能通过继承的方式

// 为牛奶的Decaf咖啡创建一个类
class DecafWithMilk{
    
}
// 为加牛奶的Espresso咖啡创建一个类
class EspressonWithMilk{
    
}
...

好像感觉到不对劲了,这才是一种调料的组合

如果我需要摩卡 + 白糖摩卡 + 牛奶 等等组合?还维护的下去吗?

这样会造成类爆炸的问题,有多少组合就会产生多少类

image-20221005002759551

如果你非常强大,硬着头皮写完了所有类,现在老板说又要加一种调料

之前是四种,有15中组合,现在五种调料,有31中,六种调料有63种

类,爆炸了!! 程序员,疯了!!

为了解决上述问题,作者尝试这样来解决:给每一种调料加上boolean属性

// 优点,没有类爆炸!
 class Beverage {
	private String description;
	private boolean milk;
	private boolean soy;
	private boolean mocha;
	private boolean bubble;

	public Beverage(String description) {
		super();
		this.description = description;
	}
	public  double cost() {
		
		double cost = 0;
		
		if(milk) {
			cost += 0.2;
		}
		if(soy) {
			cost += 0.2;
		}
		if(mocha) {
			cost += 0.3;
		}
		if(bubble) {
			cost += 0.1;
		}
		
		return cost;
	}

	public boolean ismilk() {
		return milk;
	}
	public void setmilk(boolean milk) {
		this.milk = milk;
	}
	public boolean isSoy() {
		return soy;
	}
	public void setSoy(boolean soy) {
		this.soy = soy;
	}
	public boolean isMocha() {
		return mocha;
	}
	public void setMocha(boolean mocha) {
		this.mocha = mocha;
	}
	public boolean isbubble() {
		return bubble;
	}
	public void setBubble(boolean bubble) {
		this.bubble = bubble;
	}
}
class DarkRoast extends Beverage {
	public DarkRoast(String descriptoin) {
		super(descriptoin);
	}
	
	public double cost() {
		return super.cost() + 10;
	}
}

class Decaf extends Beverage {
	public Decaf(String description) {
		super(description);
	}

	@Override
	public double cost() {
		return super.cost() + 8;
	}
}
class Espresso extends Beverage {

	public Espresso(String description) {
		super(description);
	}
	@Override
	public double cost() {
		return super.cost() + 12;
	}
	
}

class HouseBlend extends Beverage {

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

	@Override
	public double cost() {
		return super.cost() + 15;
	}
	
}
---------------------------------------------------------------------------------
//自己扩展一种饮料茶也没问题,没有违反开闭原则
class Tea extends Beverage {

	public Tea(String description) {
		super(description);
	}
	
	public double cost() {
		return super.cost() + 9;
	}
	
}

public class Test {

	public static void main(String[] args) {
		DarkRoast dr = new DarkRoast("焦炒咖啡");
		dr.setMocha(true);
		dr.setSoy(true);
		System.out.println(dr.cost());
		
		HouseBlend hb = new HouseBlend("混合咖啡");
		hb.setBubble(true);
		System.out.println(hb.cost());

		Tea tea = new Tea("凉茶");
		tea.setSoy(true);
		System.out.println(tea.cost());
	}
}

这样处理之后好像问题解决了( •̀ ω •́ )y,自己扩展一种饮料茶也没问题,没有违反开闭原则

但是现在老板又加了一种调料:枸杞

这时怎么办呢?又不能改源码,我们尝试用装饰器模式完成这个功能:

abstract class Beverage {
    private String description;

    public Beverage(String description) {
        super();
        this.description = description;
    }
    public abstract double cost();

    public String getDescription() {
        return description;
    }
}


class DarkRoast extends Beverage {

    public DarkRoast(String descriptoin) {
        super(descriptoin);
    }

    public double cost() {
        return  10;
    }
}

class Decaf extends Beverage {

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

    @Override
    public double cost() {
        return  8;
    }
}

class Espresso extends Beverage {

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

    @Override
    public double cost() {
        return 12;
    }

}

class HouseBlend extends Beverage {

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

    @Override
    public double cost() {
        return  15;
    }

}

/**
 * 这里我们让调料类继承自饮料类,显然违背了继承中的"is a"关系,但是在装饰器模式中这个原则就是需要违背
 * 尽管调料不是饮料,但是为了解决问题,我们也只能让调料去继承饮料
 */
abstract class CondimentDecorator extends Beverage {
    // 不仅需要继承饮料还需要关联饮料,让调料类关联饮料
    protected Beverage beverage;

    public CondimentDecorator(Beverage beverage) {
        super("调料");
        this.beverage = beverage;
    }

}

class Milk extends CondimentDecorator {

    public Milk(Beverage beverage) {
        super(beverage);
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.2;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + " 牛奶";
    }

}

class Mocha extends CondimentDecorator{

    public Mocha(Beverage beverage) {
        super(beverage);
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.3;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + " 摩卡";
    }

}

class Soy extends CondimentDecorator {
    public Soy(Beverage beverage) {
        super(beverage);
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.2;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + " 豆浆";
    }

}

class Bubble extends CondimentDecorator {

    public Bubble(Beverage beverage) {
        super(beverage);
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.1;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + " 奶泡";
    }

}

-------------------------------------------------------------------------

class GouQi extends CondimentDecorator {

    public GouQi(Beverage beverage) {
        super(beverage);
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.4;
    }
    @Override
    public String getDescription() {
        return beverage.getDescription() + " 枸杞";
    }

}

public class Demo03 {

    public static void main(String[] args) throws IOException {

        Beverage b = new DarkRoast("焦炒咖啡");
        //加牛奶
        Beverage b2 = new Milk(b);
        //加豆浆
        Beverage b3 = new Soy(b2);
        //加泡沫
        Beverage b4 = new Bubble(b3);
        //加第二份牛奶
        Beverage b5 = new Milk(b4);
        //加枸杞
        Beverage b6 = new GouQi(b5);

        System.out.println(b6.getDescription() + ":" +  b6.cost());
        /**
         * 焦炒咖啡 牛奶 豆浆 奶泡 牛奶 枸杞:11.099999999999998
         */
    }
}

不难发现,上述的代码做到了在不改动源代码的前提下,完成了动态添加调料的功能!

那原理是什么呢?

我们重点看这几行代码:

// 现在想要加牛奶
Milk milk = new Milk(decaf);
// 加摩卡
Mocha mocha = new Mocha(milk);

这几行就是精华了,我们称之为想要添加什么功能就用对应的类装饰就行了!,我们把这种类称为ConcreteDecorator(具体装饰类)

我们不能用传统的继承关系看上面的代码,认为 Milk是不能继承Condiment的,我们仔细看 Milk到底做了啥

class Milk extends Condiment {
    public Milk(Beverage beverage) {
        super(beverage);
    }
    ...
}

可以看到Milk不仅继承了Beverage饮料类,还关联了饮料类

  • 关联是想要获取想要装饰的对象
  • 继承是为了在原有的基础上对装饰对象的装饰

总结一下装饰器模式的几个角色

  • Component(抽象构件角色):它是具体构件和抽象装饰类的共同父类,以规范准备接受附加责任的对象(Beverage)
  • ConcreteComponent(具体构件):抽象构件角色的子类(或实现),具体的组件对象,装饰器可以给它增加额外的职责(decaf、Espresso、DrakRoast、HouseBlend)
  • Docorator(装饰器):也是抽象构件角色的子类,持有一个抽象构件角色的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的(Condiment)
  • ConcreteDecorator(具体装饰类):具体的装饰类,实现为需要装饰的构件添加新的职责(Milk、Mocha)

记住关键步骤:

  • 继承 + 依赖:继承需要装饰的类的父类,拥有共性;组合想要装饰的类,用于装饰(加强)

  • 无限套娃:通过套娃,对需要装饰的类进行装饰、加强

在这里插入图片描述

2、装饰器模式的优缺点

优点:

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

缺点:

  • 多层装饰比较复杂

3、 Jdk中的装饰器模式

jdk中用到了很多装饰器模式,例如流里面就有很多的装饰类

img

通过装饰器模式写一个MyBufferedReader来自己实现一次读一行的功能

class MyBufferedReader extends FilterReader {
    //关联
    private Reader in;
    protected MyBufferedReader(Reader in) {
        super(in);
        this.in = in;
    }
    public String readLine() throws IOException {

        StringBuilder sb = new StringBuilder("");
        int n = 0;
        while(true) {
            n = in.read();
            if(n == '\r' ) {
                continue;
            }
            if(n == '\n' || n == -1) {
                break;
            }
            sb.append((char)n);
        }
        // 流程能走到这里,n有几种可能? 一个是-1,  是\n
        if(sb.toString().length() == 0){
            if(n == '\n'){
                return "";
            }else{
                return  null;
            }
        }else{
            return sb.toString();
        }
    }

    @Override
    public void close() throws IOException {
        in.close();
    }
}
public class Test111 {
    public static void main(String[] args) throws Exception {
        Reader in = new FileReader("F:\\1.txt");
        MyBufferedReader mbr = new MyBufferedReader(in);

        String line = null;

        while((line = mbr.readLine()) != null) {
            System.out.println(line);
        }
        mbr.close();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

King Gigi.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值