您现在的位置是:首页 >技术交流 >设计模式之装饰器模式网站首页技术交流

设计模式之装饰器模式

King Gigi. 2024-06-14 17:18:13
简介设计模式之装饰器模式

我们现在来看一个业务场景,星巴克卖咖啡,一开始只有四种咖啡,分别是: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());
	}
}

但是现在又加了一种调料:枸杞,这时怎么办呢?又不能改源码。我们用装饰器模式完成这个功能:

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)

记住关键步骤:

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

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

在这里插入图片描述

装饰器模式的优缺点

优点:

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

缺点:

  • 多层装饰比较复杂
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。