通过一个现实世界的例子来理解“修饰模式”

我在研究 GOF中记录的 修饰模式

请帮我理解 修饰模式。有人能给出一个在现实世界中有用的用例示例吗?

97646 次浏览

修饰模式实现了动态添加的单一目标 任何对象的责任

以一家披萨店为例。在比萨店里,他们会出售一些比萨品种,他们也会在菜单上提供配料。现在想象一下,如果披萨店必须提供披萨和配料的每种组合的价格。即使有4个基本的比萨饼和8种不同的配料,应用程序也会疯狂地维护所有这些比萨饼和配料的具体组合。

修饰模式来了。

根据修饰模式,您将实现浇头,因为装饰器和披萨将由这些浇头的装饰器装饰。实际上,每个顾客都希望得到他想要的配料,最终的账单金额将由基础比萨饼和额外订购的配料组成。每个顶级装饰师都会知道他们正在装饰的披萨和它的价格。浇头对象的 GetPrice()方法将返回比萨饼和浇头的累计价格。

剪辑

下面是上面解释的代码示例。

public abstract class BasePizza
{
protected double myPrice;


public virtual double GetPrice()
{
return this.myPrice;
}
}


public abstract class ToppingsDecorator : BasePizza
{
protected BasePizza pizza;
public ToppingsDecorator(BasePizza pizzaToDecorate)
{
this.pizza = pizzaToDecorate;
}


public override double GetPrice()
{
return (this.pizza.GetPrice() + this.myPrice);
}
}


class Program
{
[STAThread]
static void Main()
{
//Client-code
Margherita pizza = new Margherita();
Console.WriteLine("Plain Margherita: " + pizza.GetPrice().ToString());


ExtraCheeseTopping moreCheese = new ExtraCheeseTopping(pizza);
ExtraCheeseTopping someMoreCheese = new ExtraCheeseTopping(moreCheese);
Console.WriteLine("Plain Margherita with double extra cheese: " + someMoreCheese.GetPrice().ToString());


MushroomTopping moreMushroom = new MushroomTopping(someMoreCheese);
Console.WriteLine("Plain Margherita with double extra cheese with mushroom: " + moreMushroom.GetPrice().ToString());


JalapenoTopping moreJalapeno = new JalapenoTopping(moreMushroom);
Console.WriteLine("Plain Margherita with double extra cheese with mushroom with Jalapeno: " + moreJalapeno.GetPrice().ToString());


Console.ReadLine();
}
}


public class Margherita : BasePizza
{
public Margherita()
{
this.myPrice = 6.99;
}
}


public class Gourmet : BasePizza
{
public Gourmet()
{
this.myPrice = 7.49;
}
}


public class ExtraCheeseTopping : ToppingsDecorator
{
public ExtraCheeseTopping(BasePizza pizzaToDecorate)
: base(pizzaToDecorate)
{
this.myPrice = 0.99;
}
}


public class MushroomTopping : ToppingsDecorator
{
public MushroomTopping(BasePizza pizzaToDecorate)
: base(pizzaToDecorate)
{
this.myPrice = 1.49;
}
}


public class JalapenoTopping : ToppingsDecorator
{
public JalapenoTopping(BasePizza pizzaToDecorate)
: base(pizzaToDecorate)
{
this.myPrice = 1.49;
}
}

这是一个动态地向现有对象或修饰模式添加新行为的简单示例。由于 Javascript 等动态语言的特性,这种模式成为语言本身的一部分。

// Person object that we will be decorating with logging capability
var person = {
name: "Foo",
city: "Bar"
};


// Function that serves as a decorator and dynamically adds the log method to a given object
function MakeLoggable(object) {
object.log = function(property) {
console.log(this[property]);
}
}


// Person is given the dynamic responsibility here
MakeLoggable(person);


// Using the newly added functionality
person.log('name');

值得注意的是,Java i/o 模型是基于修饰模式的。这个阅读器在那个阅读器上面的分层... ... 是一个真实世界的装潢师的例子。

什么是 Java 中的装饰器设计模式。

这个修饰模式的正式定义来自于 GoF 的书(设计模式: 可重用面向对象软件的元素,1995,培生教育,Inc。皮尔逊-艾迪生-韦斯利出版社)说你可以,

”动态地向对象附加其他职责 为扩展功能提供了一种灵活的子类化替代方案。”

假设我们有一个比萨饼,我们想用马萨拉鸡肉,洋葱和马苏里拉奶酪等配料来装饰它。让我们看看如何在 Java 中实现它..。

演示如何在 Java 中实现装饰器设计模式的程序。

比萨。爪哇:

<!-- language-all: lang-html -->


package com.hubberspot.designpattern.structural.decorator;


public class Pizza {


public Pizza() {


}


public String description(){
return "Pizza";
}


}






package com.hubberspot.designpattern.structural.decorator;


public abstract class PizzaToppings extends Pizza {


public abstract String description();


}


package com.hubberspot.designpattern.structural.decorator;


public class ChickenMasala extends PizzaToppings {


private Pizza pizza;


public ChickenMasala(Pizza pizza) {
this.pizza = pizza;
}


@Override
public String description() {
return pizza.description() + " with chicken masala, ";
}


}






package com.hubberspot.designpattern.structural.decorator;


public class MozzarellaCheese extends PizzaToppings {


private Pizza pizza;


public MozzarellaCheese(Pizza pizza) {
this.pizza = pizza;
}


@Override
public String description() {
return pizza.description() + "and mozzarella cheese.";
}
}






package com.hubberspot.designpattern.structural.decorator;


public class Onion extends PizzaToppings {


private Pizza pizza;


public Onion(Pizza pizza) {
this.pizza = pizza;
}


@Override
public String description() {
return pizza.description() + "onions, ";
}


}






package com.hubberspot.designpattern.structural.decorator;


public class TestDecorator {


public static void main(String[] args) {


Pizza pizza = new Pizza();


pizza = new ChickenMasala(pizza);
pizza = new Onion(pizza);
pizza = new MozzarellaCheese(pizza);


System.out.println("You're getting " + pizza.description());


}


}

Wikipedia 上有一个关于用滚动条装饰窗口的例子:

Http://en.wikipedia.org/wiki/decorator_pattern

下面是另一个非常“现实世界”的“团队成员、团队领导和管理者”的例子,它说明了简单的继承是无法取代修饰模式的:

Https://zishanbilal.wordpress.com/2011/04/28/design-patterns-by-examples-decorator-pattern/

示例-场景-假设您正在编写一个加密模块。这种加密方法可以使用 DES- 数据加密标准加密清晰的文件。类似地,在一个系统中你可以将加密作为 AES-高级加密标准。此外,您可以有加密的组合-首先 DES,然后 AES。或者你可以先有 AES,然后是 DES。

讨论-你将如何迎合这种情况?你不能一直创建这样的组合对象-例如-AES 和 DES-总共4个组合。因此,您需要有4个单独的对象,这将变得复杂,因为加密类型将增加。

解决方案——根据运行时的需要不断构建堆栈组合。 这种堆栈方法的另一个优点是您可以轻松地解除它。

下面是 C + + 中的解决方案。

首先,您需要一个基类——堆栈的基本单元。你可以把它当作堆栈的底部。在这个示例中,它是 clear file。让我们始终遵循多态性。首先创建这个基本单元的接口类。这样,您就可以随心所欲地实现它。另外,在包含这个基本单元时,不需要考虑依赖性。

下面是接口类-

class IclearData
{
public:


virtual std::string getData() = 0;
virtual ~IclearData() = 0;
};


IclearData::~IclearData()
{
std::cout<<"Destructor called of IclearData"<<std::endl;
}

现在,实现这个接口类-

class clearData:public IclearData
{
private:


std::string m_data;


clearData();


void setData(std::string data)
{
m_data = data;
}


public:


std::string getData()
{
return m_data;
}


clearData(std::string data)
{
setData(data);
}


~clearData()
{
std::cout<<"Destructor of clear Data Invoked"<<std::endl;
}


};

现在,让我们创建一个装饰器抽象类-它可以扩展为创建任何类型的风味-这里的风味是加密类型。这个修饰符抽象类与基类相关。因此,装饰器是一种接口类。因此,您需要使用继承。

class encryptionDecorator: public IclearData
{


protected:
IclearData *p_mclearData;


encryptionDecorator()
{
std::cout<<"Encryption Decorator Abstract class called"<<std::endl;
}


public:


std::string getData()
{
return p_mclearData->getData();
}


encryptionDecorator(IclearData *clearData)
{
p_mclearData = clearData;
}


virtual std::string showDecryptedData() = 0;


virtual ~encryptionDecorator() = 0;


};


encryptionDecorator::~encryptionDecorator()
{
std::cout<<"Encryption Decorator Destructor called"<<std::endl;
}

现在,让我们做一个混凝土装饰类- 加密类型-AES-

const std::string aesEncrypt = "AES Encrypted ";


class aes: public encryptionDecorator
{


private:


std::string m_aesData;


aes();


public:


aes(IclearData *pClearData): m_aesData(aesEncrypt)
{
p_mclearData = pClearData;
m_aesData.append(p_mclearData->getData());
}


std::string getData()
{
return m_aesData;
}


std::string showDecryptedData(void)
{
m_aesData.erase(0,m_aesData.length());
return m_aesData;
}


};

现在,我们假设装饰类型是 DES-

const std::string desEncrypt = "DES Encrypted ";


class des: public encryptionDecorator
{


private:


std::string m_desData;


des();


public:


des(IclearData *pClearData): m_desData(desEncrypt)
{
p_mclearData = pClearData;
m_desData.append(p_mclearData->getData());
}


std::string getData(void)
{
return m_desData;
}


std::string showDecryptedData(void)
{
m_desData.erase(0,desEncrypt.length());
return m_desData;
}


};

让我们创建一个客户端代码来使用这个装饰器类-

int main()
{
IclearData *pData = new clearData("HELLO_CLEAR_DATA");


std::cout<<pData->getData()<<std::endl;




encryptionDecorator *pAesData = new aes(pData);


std::cout<<pAesData->getData()<<std::endl;


encryptionDecorator *pDesData = new des(pAesData);


std::cout<<pDesData->getData()<<std::endl;


/** unwind the decorator stack ***/
std::cout<<pDesData->showDecryptedData()<<std::endl;


delete pDesData;
delete pAesData;
delete pData;


return 0;
}

您将看到以下结果-

HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
DES Encrypted AES Encrypted HELLO_CLEAR_DATA
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Destructor called
Destructor called of IclearData
Encryption Decorator Destructor called
Destructor called of IclearData
Destructor of clear Data Invoked
Destructor called of IclearData

下面是 UML 图-它的类表示。在这种情况下,您可以跳过代码,将重点放在设计方面。

enter image description here

这个修饰模式可以让你动态地为对象添加行为。

让我们举一个例子,你需要建立一个应用程序来计算不同种类的汉堡包的价格。您需要处理不同的汉堡包,如“大”或“与奶酪”,其中每个有一个价格相对于基本的汉堡包。例如,加奶酪的汉堡加10美元,大的汉堡加15美元,等等。

在这种情况下,你可能会想要创建一些子类来处理这些问题:

class Burger
def price
50
end
end


class BurgerWithCheese < Burger
def price
super + 15
end
end

在上面的示例中,BurgerWithCheese 类继承自 Burger,并重写 price 方法以向超类中定义的 price 添加 $15。您还可以创建一个 LargeBurger 类并定义相对于 Burger 的价格。但是您还需要为“大”和“带奶酪”的组合定义一个新类。

如果我们要提供“汉堡加薯条”怎么办?我们已经有4个类处理这些组合,我们将需要增加4个以处理所有组合的3个属性-“大”,“与奶酪”和“与炸薯条”。我们现在需要八节课。再加一处房产,我们就需要16个。这个增长为2 ^ n。

相反,让我们尝试定义一个包含 Burger 对象的 BurgerDecorator:

class BurgerDecorator
def initialize(burger)
self.burger = burger
end
end


class BurgerWithCheese < BurgerDecorator
def price
self.burger.price + 15
end
end


burger = Burger.new
cheese_burger = BurgerWithCheese.new(burger)
cheese_burger.price   # => 65

在上面的示例中,我们创建了一个 BurgerDecorator 类,BurgerWithCheese 类从该类继承。我们还可以通过创建 LargeBurger 类来表示“大”变量。现在我们可以定义一个在运行时有奶酪的大汉堡包:

b = LargeBurger.new(cheese_burger)
b.price  # => 50 + 15 + 20 = 85

还记得如何使用继承添加“与薯条”变化将涉及添加4个以上的子类?使用装饰器,我们只需创建一个新类 BurgerWithFries 来处理新的变体并在运行时处理它。每个新的属性只需要更多的装饰来覆盖所有的排列。

附言。这是我写的一篇关于 用露比的修饰模式的文章的简短版本,如果您希望找到更详细的示例,可以阅读这篇文章。

装饰设计模式 : 此模式有助于在运行时修改对象的特征。它为一个物体提供了不同的味道,并且可以灵活地选择我们想要在这种味道中使用的成分。

现实生活例子: 假设你在飞机上有一个主座位。现在您可以选择多种设施与座位。每种设施都有自己的成本。现在,如果用户选择 Wifi 和优质食品,他/她将收取座位 + Wifi + 优质食品。

enter image description here

在这种情况下,装饰设计模式可以真正帮助我们。你可浏览以上连结,了解更多有关修饰模式及实际应用的例子。

设计师:

  1. 在运行时向对象添加行为 。继承是实现此功能的关键,这是此模式的优点和缺点。
  2. 增强了界面的 行为
  3. 修饰符可以看作是只有一个组件的简并 合成。但是,Decorator 增加了额外的职责——它不是用于对象聚合的。
  4. Decorator 类声明与 LCD (最低类分母)接口的组合关系,并且该数据成员在其构造函数中初始化。
  5. Decorator 的设计目的是让您不用子类化就可以向对象添加责任

有关详细信息,请参阅 源头挖掘文章。

Decorator (摘要) : 它是一个抽象类/接口,实现组件接口。它包含组件接口。在没有这个类的情况下,对于不同的组合,需要很多 ConcreteDecorators 的子类。组件的组合减少了不必要的子类。

JDK 的例子:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("a.txt")));
while(bis.available()>0)
{
char c = (char)bis.read();
System.out.println("Char: "+c);;
}

看看下面关于 UML 图和代码示例的 SE 问题。

输氧修饰模式

有用文章:

Journaldev

维基百科

修饰模式的真实例子:

何时使用修饰模式?

Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
beverage.decorateBeverage();


beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
beverage.decorateBeverage();

在上面的例子中,Tea or Coffee (Beverage)已经被 Sugar 和 Lemon 修饰过了。

修饰模式可以通过链接该对象的其他类似子类来帮助您更改或配置该对象的功能。

最好的例子是 java.io 包中的 InputStream 和 OutputStream 类

    File file=new File("target","test.txt");
FileOutputStream fos=new FileOutputStream(file);
BufferedOutputStream bos=new BufferedOutputStream(fos);
ObjectOutputStream oos=new ObjectOutputStream(bos);




oos.write(5);
oos.writeBoolean(true);
oos.writeBytes("decorator pattern was here.");




//... then close the streams of course.

修饰模式达到了 abc0的单一目标。

Java I/O 模型 是基于修饰模式的。

Java IO as decorator pattern

不久前,我将一个代码库重构为使用修饰模式,因此我将尝试解释这个用例。

假设我们有一组服务,并且基于用户是否获得了特定服务的许可证,我们需要启动该服务。

所有服务都有一个公共接口

interface Service {
String serviceId();
void init() throws Exception;
void start() throws Exception;
void stop() throws Exception;
}

预重构

abstract class ServiceSupport implements Service {
public ServiceSupport(String serviceId, LicenseManager licenseManager) {
// assign instance variables
}


@Override
public void init() throws Exception {
if (!licenseManager.isLicenseValid(serviceId)) {
throw new Exception("License not valid for service");
}
// Service initialization logic
}
}

如果仔细观察,ServiceSupport依赖于 LicenseManager。但是为什么它要依赖于 LicenseManager呢?如果我们需要的后台服务不需要检查许可证信息。在目前的情况下,我们将不得不以某种方式训练 LicenseManager返回 true作为后台服务。 这种方法对我来说不太好,根据我的理解,许可证检查和其他逻辑是相互正交的。

因此,修饰模式出手相救,并在这里开始使用 TDD 进行重构。

后重构

class LicensedService implements Service {
private Service service;
public LicensedService(LicenseManager licenseManager, Service service) {
this.service = service;
}


@Override
public void init() {
if (!licenseManager.isLicenseValid(service.serviceId())) {
throw new Exception("License is invalid for service " + service.serviceId());
}
// Delegate init to decorated service
service.init();
}


// override other methods according to requirement
}


// Not concerned with licensing any more :)
abstract class ServiceSupport implements Service {
public ServiceSupport(String serviceId) {
// assign variables
}


@Override
public void init() {
// Service initialization logic
}
}


// The services which need license protection can be decorated with a Licensed service
Service aLicensedService = new LicensedService(new Service1("Service1"), licenseManager);
// Services which don't need license can be created without one and there is no need to pass license related information
Service aBackgroundService = new BackgroundService1("BG-1");

外卖

  • 代码的内聚性更好了
  • 单元测试变得更加容易,因为在测试 ServiceSupport 时不必模拟许可
  • 不需要通过任何特殊的背景服务检查绕过许可
  • 适当分工

让我们以 PubG 为例。突击步枪最好的工程与4倍变焦,当我们在它上面,我们还需要补偿器和抑制器。它将减少后坐力,减少发射声音以及回声。我们将需要实现这个功能,我们将允许玩家购买他们最喜欢的枪和他们的配件。玩家可以购买枪支或部分配件或全部配件,他们将收取相应的费用。

让我们看看修饰模式是如何运用的:

假设有人想购买 SCAR-L 与所有三个附件上述。

  1. 取 SCAR-L 的一个对象
  2. 用4倍缩放对象装饰(或添加) SCAR-L
  3. 用抑制器对象装饰 SCAR-L
  4. 用压缩器对象装饰 SCAR-L
  5. 调用 Cost 方法并让每个对象委托添加成本 采用配件成本法

这将导致类图如下:

Decorator pattern at work

现在,我们可以这样上课:

public abstract class Gun {
private Double cost;
public Double getCost() {
return cost;
}
}


public abstract class GunAccessories extends Gun {  }


public class Scarl extends Gun {
public Scarl() {
cost = 100;
}
}


public class Suppressor extends GunAccessories {
Gun gun;
public Suppressor(Gun gun) {
cost = 5;
this.gun = gun;
}
public double getCost(){
return cost + gun.getCost();
}
}


public class GunShop{
public static void main(String args[]){
Gun scarl = new Scarl();
scarl = new Supressor(scarl);
System.out.println("Price is "+scarl.getCost());
}
}

我们同样可以添加其他配件,并装饰我们的枪。

参考文献:

Https://nulpointerexception.com/2019/05/05/a-beginner-guide-to-decorator-pattern/

修饰符只是子类化的一种组合替代方法。 每个人都提到的关于这个主题的原著中的一个常见示例是一个文本处理应用程序。

假设你写了一段话。你把它标成黄色。你用斜体写了一个句子。你加粗了斜体字句子的一半,也加粗了下一个句子的一半。您增加其中一个斜体和粗体字母的字体大小。你改变了一半突出显示部分的字体样式,有些变成了斜体部分,有些没有..。

所以我要问你如何实现这个功能。您从一个简单的、未修饰的字母类开始。接下来你要做什么?

我假设您不会使用子类化。你需要这样一个复杂的、令人费解的多重继承层次结构来实现我所描述的所有组合,而子类化和多重继承化则是荒谬的。我认为这不需要解释。

您可能会建议将所有这些属性打包到字母对象中。属性来定义字体样式、大小、突出显示、粗体、斜体等等。每种可以添加到字母对象的属性,都在字母类中为其添加了一个属性。

那么,这种基于属性的方法存在哪些问题呢?

  1. 现在您的类已经很庞大了,它占用了大量的内存。它有所有这些不必要的属性与它相关联,大多数它将永远不会使用。大多数字母只是... 字母。没有装饰。
  2. 您的字母类的数据正在以一种完全公开的方式使用,您的类只是一个经过美化的结构。对于所有这些属性,都有许多 getter 和 setter。外部代码访问这些设置器并修改对象的图形外观。对象和外部代码之间存在紧密耦合。
  3. 所有东西都装在一个地方,不是模块化的。它只是一个膨胀的、相互连接的代码包。在处理字母对象的外部代码中也是如此。

从根本上说,这是一个面向对象设计、适当封装和关注点分离的问题。

现在,让我们理所当然地认为我们想要使用更好的 OO 设计原则。我们希望使用封装,我们希望在外部代码和字母类之间保持松散耦合。我们希望最小化字母对象的内存占用。怎么... ?我们不能使用子类化..。

因此,我们使用装饰器,这是一种面向对象设计的组合方法-它与自顶向下的子类化方法有点相反。您可以在运行时使用更多功能来包装这些字母对象,并在它们的基础上构建它们。

这就是修饰模式-它是子类的一种组合替代。在我们的示例中,您向字母对象添加了一个需要突出显示的装饰器。您可以以任意数量的方式组合任意数量的装饰器,并将它们全部包围在给定的字母周围。装饰器界面总是透明的,所以从外部看这些字母仍然是一样的。

任何时候,如果需要以任意和可重组的方式增强功能,请考虑这种方法。多重继承会遇到各种各样的问题,这是不可扩展的。