【发布时间】:2016-08-16 00:13:43
【问题描述】:
【问题讨论】:
-
您可以在这里找到一些 Java API 中的真实示例:stackoverflow.com/questions/1673841/…
-
一篇通过简单示例展示装饰器模式好处的文章:dzone.com/articles/is-inheritance-dead
【问题讨论】:
装饰器模式实现了动态添加的单一目标 对任何对象的责任。
考虑一个比萨店的案例。在比萨店,他们将出售几种比萨,他们还将在菜单中提供配料。现在想象这样一种情况,如果比萨店必须为比萨饼和配料的每种组合提供价格。即使有四种基本的比萨饼和 8 种不同的浇头,应用程序也会疯狂地维护所有这些比萨饼和浇头的具体组合。
装饰器模式来了。
根据装饰器模式,您将实现浇头作为装饰器,比萨饼将由这些浇头的装饰器装饰。实际上,每个顾客都想要他想要的配料,最终的账单金额将由基础比萨饼和额外订购的配料组成。每个浇头装饰师都会知道它正在装饰的比萨饼及其价格。 Topping 对象的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;
}
}
【讨论】:
装饰器只是子类化的组合替代品。 每个人都提到的关于这个主题的原始书中的常见示例是文本处理应用程序。
假设你写了一个段落。您将其突出显示为黄色。你把一个句子斜体。你将斜体句子的一半加粗,下一个句子的一半也加粗。您增加其中一个斜体和粗体字母的字体大小。您更改了一半突出显示部分的字体样式,其中一些进入斜体部分,有些则没有...
所以我要问你如何实现该功能。你从一个简单的、未装饰的字母的类开始。你接下来要做什么?
我假设您不会使用子类化。您将需要如此复杂、错综复杂的多重继承层次结构来实现我所描述的所有组合以及更多,子类化和多重继承将是荒谬的。我认为这不需要解释。
您可能会建议将所有这些属性打包到您的信函对象中。用于定义字体样式、大小、突出显示、粗体、斜体的属性,不胜枚举。您可以添加到字母对象的每一种属性,您在字母类中都有一个属性。
那么这种基于属性的方法有什么问题呢?
从根本上说,这是一个面向对象设计、适当封装和关注点分离的问题。
现在,让我们想当然地认为我们想使用更好的 OO 设计原则。我们想使用封装,我们想保持外部代码和我们的字母类之间的松散耦合。我们希望最小化我们的字母对象内存占用。如何...?我们不能使用子类化...
所以我们使用装饰器,这是面向对象设计的一种组合方法——它与自上而下的子类化方法有点相反。您可以在运行时用更多功能包装这些字母对象,并在它们之上构建。
这就是装饰器模式 - 它是子类化的组合替代方案。在我们的示例中,您将装饰器添加到需要突出显示的字母对象。您可以以任意数量的方式组合任意数量的装饰器,并将它们包裹在给定的字母周围。装饰器界面始终是透明的,因此您仍然从外部将这些字母视为相同。
任何时候您需要以任意和可重组的方式增强功能,请考虑这种方法。多重继承会遇到各种各样的问题,它只是不可扩展。
【讨论】:
示例 - 场景 - 假设您正在编写一个加密模块。这种加密可以使用 DES - 数据加密标准加密明文文件。同样,在系统中,您可以将加密作为 AES - 高级加密标准。此外,您可以组合加密 - 首先是 DES,然后是 AES。或者您可以先使用 AES,然后使用 DES。
讨论-你将如何应对这种情况?您不能继续创建此类组合的对象 - 例如 - AES 和 DES - 总共 4 个组合。因此,您需要有 4 个单独的对象。随着加密类型的增加,这将变得复杂。
解决方案 - 继续构建堆栈 - 根据需要组合 - 在运行时。 这种堆栈方法的另一个优点是您可以轻松展开它。
这是解决方案 - 用 C++ 编写。
首先,您需要一个基类 - 堆栈的基本单元。您可以将其视为堆栈的基础。在本例中,它是明文文件。让我们始终遵循多态性。首先制作这个基本单元的接口类。这样,您可以随心所欲地实现它。此外,在包含此基本单元时,您无需考虑依赖关系。
这里是接口类-
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 图 - 它的类表示。如果您想跳过代码并专注于设计方面。
【讨论】:
strategy pattern吗?
这是一个向现有对象动态添加新行为的简单示例,或装饰器模式。由于 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');
【讨论】:
switch 或简单的if 中,您可以声称这是一个为类动态添加行为的好例子。但是,我们至少需要两个类来定义装饰器和装饰对象这种模式。
让我们以 PubG 为例。突击步枪最适合使用 4 倍变焦,当我们使用它时,我们还需要补偿器和抑制器。它将减少后坐力并减少射击声音和回声。我们需要实现这个功能,允许玩家购买他们最喜欢的枪和配件。玩家可以购买枪支或部分配件或全部配件,并收取相应费用。
让我们看看这里是如何应用装饰器模式的:
假设有人想购买带有上述所有三个配件的 SCAR-L。
这将导致这样的类图:
现在,我们可以有这样的类:
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/
【讨论】:
前段时间我将代码库重构为使用装饰器模式,因此我将尝试解释用例。
假设我们有一组服务,根据用户是否获得特定服务的许可,我们需要启动服务。
所有服务都有一个通用接口
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");
【讨论】:
装饰者:
详情请参阅sourcemaking 文章。
装饰器(抽象):它是一个抽象类/接口,它实现了组件接口。它包含组件接口。在没有这个类的情况下,你需要很多 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);;
}
查看下面的 SE 问题,了解 UML 图和代码示例。
有用的文章:
装饰器模式的真实例子:VendingMachineDecorator已经解释了@
When to Use the Decorator Pattern?
Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
beverage.decorateBeverage();
beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
beverage.decorateBeverage();
在上面的示例中,茶或咖啡(饮料)已由糖和柠檬装饰。
【讨论】:
装饰器模式通过链接该对象的其他类似子类来帮助您更改或配置对象的功能。
最好的例子是 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.
【讨论】:
维基百科上有一个关于用滚动条装饰窗口的例子:
http://en.wikipedia.org/wiki/Decorator_pattern
这是另一个非常“真实世界”的“团队成员、团队领导和经理”示例,它说明了装饰器模式无法用简单的继承来替代:
https://zishanbilal.wordpress.com/2011/04/28/design-patterns-by-examples-decorator-pattern/
【讨论】:
Decorator Design Pattern: 这种模式有助于在运行时修改对象的特征。它为对象提供不同的风味,并让我们可以灵活地选择我们想要在该风味中使用的成分。
现实生活中的例子: 假设您在航班上有一个主舱座位。现在您可以选择座椅的多种便利设施。每个便利设施都有与之相关的成本。现在如果用户选择Wifi和高级食物,他/她将被收取座位+wifi+高级食物的费用。
在这种情况下,装饰器设计模式真的可以帮助我们。访问上面的链接,了解更多关于装饰器模式和一个现实生活示例的实现。
【讨论】:
装饰器模式让您可以动态地向对象添加行为。
让我们举个例子,您需要构建一个计算不同种类汉堡价格的应用。您需要处理汉堡的不同变体,例如“大”或“加奶酪”,每个汉堡都具有相对于基本汉堡的价格。例如。加奶酪的汉堡加 10 美元,大汉堡加 15 美元,等等。
在这种情况下,您可能很想创建子类来处理这些问题。我们可以在 Ruby 中将其表示为:
class Burger
def price
50
end
end
class BurgerWithCheese < Burger
def price
super + 15
end
end
在上面的例子中,BurgerWithCheese 类继承自 Burger,并重写了 price 方法,将 $15 加到超类中定义的价格上。您还将创建一个 LargeBurger 类并定义相对于 Burger 的价格。但您还需要为“大”和“加奶酪”的组合定义一个新类。
现在如果我们需要提供“汉堡配薯条”会发生什么?我们已经有 4 个类来处理这些组合,我们需要再添加 4 个来处理 3 个属性的所有组合——“大”、“奶酪”和“薯条”。我们现在需要 8 节课。添加另一个属性,我们需要 16。这将增长为 2^n。
相反,让我们尝试定义一个 BurgerDecorator 来接收一个 Burger 对象:
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
还记得如何使用继承来添加“with fries”变体将涉及添加 4 个以上的子类吗?使用装饰器,我们只需创建一个新类 BurgerWithFries 来处理新的变体并在运行时处理它。每个新属性只需要更多的装饰器来覆盖所有排列。
PS。这是我写的一篇关于using the Decorator Pattern in Ruby的文章的简短版本,如果你想找到更详细的例子,可以阅读。
【讨论】:
什么是 Java 中的装饰器设计模式。
GoF 书籍(Design Patterns: Elements of Reusable Object-Oriented Software, 1995, Pearson Education, Inc. Publishing as Pearson Addison Wesley)中对装饰器模式的正式定义表明您可以,
“动态地为对象附加额外的职责。装饰器 为扩展功能提供一种灵活的替代子类的方法。”
假设我们有一个比萨饼,我们想用鸡肉玛萨拉、洋葱和马苏里拉奶酪等配料来装饰它。让我们看看如何在 Java 中实现它......
演示如何在 Java 中实现装饰器设计模式的程序。
Pizza.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());
}
}
【讨论】:
值得注意的是,Java i/o 模型基于装饰器模式。这个阅读器在那个阅读器之上的分层......是一个真实世界的装饰器示例。
【讨论】: