装饰器模式,这个设计模式其实和它的名字一样,非常容易理解。
想象一下,每天出门的时候,我们都会思考今天穿什么。睡**衣、睡裤加拖鞋,还是西装、领带加皮鞋?又或者说是,背心、短裤不穿鞋?**穿什么,不穿什么,都是可以随意更改的。而这,就是装饰器模式所应用的场景。
装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
装饰模式的实现中,我们需要一系列组件:抽象组件(Component)、具体组件(ConcreteComponent)以及装饰抽象类(Decorator)。
抽象组件定义了一个对象接口,可以给这些对象动态添加职责。具体组件是被装饰的对象,这个对象可以被一或多个装饰类装饰。装饰类即装饰抽象类,继承了抽象组件,并持有一个具体组件的引用,可以调用具体组件的方法,并可以在调用前后增加新的功能。
我们通过一个生活中的例子来具体理解:假设我们在一个咖啡店,首先会点一杯基础的咖啡,也就是ConcreteComponent;然后我们可能会要求加一份牛奶或者糖,这些就是Decorator,他们继承了咖啡接口,并且持有咖啡的引用,每次加一份牛奶或者糖的时候,我们其实是在装饰我们的咖啡。非常抱歉,我疏忽了。现在让我们继续刚才的咖啡店例子,我将用Java来说明装饰器模式是怎样工作的。
首先,我们需要定义一个抽象咖啡类(Beverage):
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
然后,我们定义一种具体的咖啡(Espresso):
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
public double cost() {
return 1.99;
}
}
接下来,我们需要定义一个抽象的装饰者(CondimentDecorator):
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
最后,我们定义一种具体的装饰者,也就是我们的“加牛奶”操作(Milk):
public class Milk extends CondimentDecorator {
Beverage beverage;
public Milk(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
public double cost() {
return .10 + beverage.cost();
}
}
这样,当我们想要一杯加了牛奶的浓缩咖啡时,我们可以这样做:
Beverage beverage = new Espresso();
beverage = new Milk(beverage);
System.out.println(beverage.getDescription() + " $" + beverage.cost());
这就是装饰器模式的工作原理。你可以发现,通过这种方式,我们可以动态地为咖啡添加各种配料,而不需要修改原来的咖啡类。而且,由于所有的配料和咖啡都继承自同一个父类,我们可以将咖啡和配料无缝地组合在一起。
在Go里面,我们会使用一些嵌入和接口的技巧。
首先定义咖啡接口:
type Beverage interface {
Cost() float64
Description() string
}
然后定义一个基础的Espresso咖啡:
type Espresso struct {}
func (e Espresso) Cost() float64 {
return 1.99
}
func (e Espresso) Description() string {
return "Espresso"
}
接下来定义一个装饰者接口:
type CondimentDecorator struct {
Beverage
}
最后我们来定义一种装饰者,也就是我们的加牛奶操作:
type Milk struct {
CondimentDecorator
}
func (m Milk) Cost() float64 {
return 0.1 + m.Beverage.Cost()
}
func (m Milk) Description() string {
return m.Beverage.Description() + ", Milk"
}
我们就可以通过下面的方式来得到一杯加了牛奶的咖啡:
beverage := Espresso{}
bev_with_milk := Milk{CondimentDecorator: beverage}
fmt.Printf("%s $%.2f\n", bev_with_milk.Description(), bev_with_milk.Cost())
我们可以发现,通过这种方式,我们可以动态地为咖啡添加各种配料,而不需要修改原来的咖啡类。而且,由于所有的配料和咖啡都实现自同一个接口,我们可以将咖啡和配料无缝地组合在一起。
装饰模式是设计模式中极其重要的一员,因为它的灵活性和弹性。这种模式适用于需要动态、透明地给对象添加职责的应用。只要理解得当,其使用的场景其实非常广泛。
然而,能力越大,责任越大。装饰模式也有其需要注意的地方。使用时要避免出现繁复和复杂的装饰层,否则不仅代码难以维护,更可能会出现各种疏漏和陷阱。
和造物者模式有什么区别?
装饰模式和造物者模式虽然都属于创建型模式,但区别在于:装饰模式关注的是给已有的对象添加功能,实现功能的可拓展,而不改变原有对象的结构;造物者模式则更多关注对象的创建过程,将对象的构造与表示分离,使同样的构建过程可以创建不同的表示。
简单说,装饰模式注重的是“动态扩展”,造物者模式注重的是“构造控制”。
和策略模式有什么区别?
装饰模式和策略模式在实现灵活性方面有些相似,但它们的应用场景和目标是不同的。
装饰模式主要用于动态地扩展一个对象的功能,而策略模式则主要用于抽象化一组算法,从而可以在运行时动态地选择算法。在装饰模式中,装饰者和被装饰对象通常有共同的超类,而在策略模式中,策略类和使用策略的上下文类通常没有共同的超类。
简单说,装饰者模式是结构型模式,注重扩展对象的功能;策略模式则是行为型模式,注重改变对象的行为。
如果上面的内容对你有帮助,请点赞收藏哦,我会分享更多的经验~