反例都是为了和装饰器模式进行对比,突出其优点,若想直接查看装饰器模式的“真身”,可直接跳到装饰器模式目录
装饰器模式
简述:装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
业务场景:星巴克卖咖啡,一开始只有4种咖啡
Decaf:无咖啡因咖啡
Espresso:特浓咖啡
DarkRoast:焦炒咖啡
HouseBlend:混合咖啡
因所有咖啡都有共性,所有开发人员,把它们的共性上提到一个父类中:Beverage
反例 #1
abstract class Beverage {
private String description;
public Beverage(String description) {
this.description = description;
}
public abstract double cost();
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
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 1.5;
}
}
class DarkRoast extends Beverage {
public DarkRoast() {
super("焦炒咖啡");
}
@Override
public double cost() {
return 2;
}
}
class HouseBlend extends Beverage {
public HouseBlend() {
super("混合咖啡");
}
@Override
public double cost() {
return 3;
}
}
//==============================================
public class AppTest {
public static void main(String[] args) {
Beverage b = new Decaf();
Beverage b2 = new Espresso();
Beverage b3 = new DarkRoast();
Beverage b4 = new HouseBlend();
System.out.println(b.getDescription() + ":" + b.cost());
System.out.println(b.getDescription() + ":" + b2.cost());
System.out.println(b.getDescription() + ":" + b3.cost());
System.out.println(b.getDescription() + ":" + b4.cost());
}
}
如此设计存在问题:
星巴克的老板为了提高自身的竞争力,想出了一个新的业务:调料。也就是可以给咖啡中放入调料:牛奶、豆浆、摩卡、泡沫
如何设计应对这种变化呢?
。。。。。。
反例 #2
为了满足上面需求中的变化:可以尝试这样解决问题:
//为加牛奶的Decaf咖啡创建一个类
class DecafWithMilk {
}
// 为加牛奶的Espresso咖啡创建一个类
class EspressoWithMilk {
}
// 为加牛奶且加豆浆的Decaf咖啡创建一个类
class DecafWithMilkAndSoy {
}
......
很明显,这样设计会导致类爆炸式增长,因此不能如此设计。
反例 #3
针对于反例2的问题,我们何必为每一种咖啡每加一种调料都创建一个类呢?这样太2了、太笨了!
我们可以直接在父类Beverage中,添加4个boolean属性,分别代表是否加了对应的4种调料!
abstract class Beverage {
private String description;
private boolean milk,soy,mocha,bubble;
public Beverage(String description) {
this.description = description;
}
public double cost() {
double total = 0;
if (milk) {
total += 0.2;
}
if (soy) {
total += 0.3;
}
if (mocha) {
total += 0.4;
}
if (bubble) {
total += 0.1;
}
return total;
}
public String getDescription() {
if (milk) {
description += " 牛奶";
}
if (soy) {
description += " 豆浆";
}
if (mocha) {
description += " 摩卡";
}
if (bubble) {
description += " 气泡";
}
return description;
}
public void setDescription(String description) { this.description = description; }
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 Decaf extends Beverage {
public Decaf() {
super("无咖啡因咖啡");
}
@Override
public double cost() {
// 咖啡本身价格 + 调料价格
return 1 + super.cost();
}
}
class Espresso extends Beverage {
public Espresso() {
super("特浓咖啡");
}
@Override
public double cost() {
return 1.5 + super.cost();
}
}
class DarkRoast extends Beverage {
public DarkRoast() {
super("焦炒咖啡");
}
@Override
public double cost() {
return 2 + super.cost();
}
}
class HouseBlend extends Beverage {
public HouseBlend() {
super("混合咖啡");
}
@Override
public double cost() {
return 3 + super.cost();
}
}
//====================客户端==========================
public class AppTest {
public static void main(String[] args) {
Beverage b = new Decaf();
b.setMilk(true);
b.setSoy(true);
System.out.println(b.getDescription() + ":" + b.cost());
}
}
优点:
1. 类没有爆炸式增长
2. 若星巴克的老板又加入了一个新的饮料:茶,不会带来大大影响
//====================客户端==========================
// 新增饮料
class Tea extends Beverage {
public Tea() {
super("茶");
}
public double cost() {
return 2;
}
}
public class AppTest {
public static void main(String[] args) {
Beverage b2 = new Tea();
System.out.println(b2.getDescription() + ":" + b2.cost());
}
}
缺点:
- 星巴克的老板新加入一种调料:枸杞,就要重新改写父类Berverage的
cost
方法和getdisecription
方法,从而将枸杞加进去,违反了开闭原则。
装饰器模式
package com.aluem.m_decorator.d;
/**
* 业务场景:星巴克卖咖啡,一开始只有4种咖啡
*/
//----------------饮料父类----------------
abstract class Beverage {
// description ==> 无咖啡因咖啡
private String description;
public Beverage(String description) {
this.description = description;
}
public abstract double cost();
public String getDescription() {
return description;
}
}
/**
* 判断两个类之间能不能有继承关系,主要看这两个类之间有没有“is a”关系。并且要符合里氏替换原则。
* 以上只是原则,不是语法强制的,也就是说在特定情况下,可以违反这个规则,在装饰器模式中就是这样。
* 尽管调料不是饮料,但是为了制作出装饰器模式,我们也要让调料去继承饮料。
*/
//----------------调料父类----------------
abstract class Condiment extends Beverage {
protected Beverage beverage;
public Condiment(Beverage beverage) {
super("调料");
this.beverage = beverage;
}
// 关联Beverage类是为了在该处使用传入的子类方法
public double cost() {
return beverage.cost();
}
}
//----------------饮料----------------
class Decaf extends Beverage {
public Decaf() {
super("无咖啡因咖啡");
}
@Override
public double cost() {
return 3;
}
}
//----------------调料----------------
class Milk extends Condiment {
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 1;
}
@Override
public String getDescription() {
return beverage.getDescription() + " 牛奶";
}
}
class Soy extends Condiment {
public Soy(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 0.5;
}
@Override
public String getDescription() {
return beverage.getDescription() + " 豆浆";
}
}
//===================客户端===========================
// 1.加入新的饮料
class Tea extends Beverage {
public Tea() {
super("茶");
}
@Override
public double cost() {
return 2;
}
}
// 2.加入新的调料
class GouQi extends Condiment {
public GouQi(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + " 枸杞";
}
@Override
public double cost() {
return beverage.cost() + 1;
}
}
public class AppTest {
public static void main(String[] args) {
Beverage beverage = new Decaf();
Beverage beverage2 = new Milk(beverage);
Beverage beverage3 = new Soy(beverage2);
System.out.println(beverage3.getDescription() + ":" + beverage3.cost());
}
}
执行示意图
优点:
- 加入新的饮料,不会违反开闭原则
- 加入新的调料,也不会违反开闭原则
//===================客户端===========================
// 1.加入新的饮料
class Tea extends Beverage {
public Tea() {
super("茶");
}
@Override
public double cost() {
return 2;
}
}
public class AppTest {
public static void main(String[] args) {
Beverage beverage;
beverage = new Tea(); // 无咖啡因咖啡
beverage = new Milk(beverage);
beverage = new Milk(beverage);
System.out.println(beverage.getDescription() + ": " + beverage.cost());
}
}
//===================客户端===========================
// 2.加入新的调料
class GouQi extends Condiment {
public GouQi(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return super.getDescription() + " 枸杞";
}
@Override
public double cost() {
return super.cost() + 2;
}
}
public class AppTest {
public static void main(String[] args) {
Beverage beverage;
beverage = new Tea(); // 无咖啡因咖啡
beverage = new Milk(beverage);
beverage = new GouQi(beverage);
System.out.println(beverage.getDescription() + ": " + beverage.cost());
}
}
UML类图
缺点:
由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
JDK源码中的装饰器模式展现
jdk中的流,就是使用装饰器模式
public class AppTest {
public static void main(String[] args) throws IOException {
//InputStream 抽象组件 ==> 饮料
//FileInputStream 具体组件 ==> 无咖啡因咖啡
InputStream in = new FileInputStream("/home/aluem/软件著作权任务安排.txt");
//BufferedInputStream 装饰器 ==> 牛奶、豆浆
BufferedInputStream bis = new BufferedInputStream(in);
InputStreamReader isr = new InputStreamReader(bis);
isr.close();
}
}
从上面的代码可以大致看出装饰器的形式,Inputstream类的子类为具体组件(无咖啡因咖啡)与一个抽象装饰器
那么其抽象装饰器是什么呢?抽象装饰器是关联与继承抽象组件InputSream的,我们从类关系中找到FilterInputStream类:
从源码分析,FilterInputStream(抽象装饰器:调料)类确实关联且继承Inputsream(抽象组件:饮料)类
从而Filterinputstream的子类为(装饰器:牛奶,豆浆。。)
装饰java.io类UML类图
装饰器模式的简单实用
public class AppTest2 {
public static void main(String[] args) throws Exception {
Reader in = new FileReader("/home/aluem/1.txt");
//关联Reader是为了使用Reader的功能,继承Reader是为了自己本身可以作为Reader传到别的装饰器中
BufferedReader br = new BufferedReader(in);//BufferedReader可以读行,而读行需要使用FileReader的读取字符的功能,因此使用装饰器模式
//BufferedReader br2 = new BufferedReader(br);
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
}
上面是JDK中的BufferedReader,我们通过装饰器模式自己创建一个新的BufferredReader模拟原来的。
public class MyBufferReader extends Reader {
private Reader in;
public MyBufferReader(Reader in){
this.in = in;
}
public String readLine() throws IOException {
StringBuilder sb = new StringBuilder();
int read;
while(true){
read = in.read();
if (read == '\r')
continue;
if (read == '\n' || read == -1){
break;
}
sb.append((char)read);
}
if (sb.toString().length() == 0){
if (read == '\n'){
return "";
}else {
return null;
}
}else {
return sb.toString();
}
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
return 0;
}
@Override
public void close() throws IOException {
in.close();
}
/*===================客户端===========================*/
public static void main(String[] args) throws IOException {
Reader in = new FileReader("E:\\1.txt");
MyBufferReader myBufferReader = new MyBufferReader(in);
// BufferedReader br = new BufferedReader(myBufferReader); 可以层层进行套娃包装
String line;
while(( line = myBufferReader.readLine() ) != null){
System.out.println(line);
}
}
}
参考资料:Java设计模式-Mr.high、《设计模式就该这样学》