装饰器模式与继承的例子

Posted

技术标签:

【中文标题】装饰器模式与继承的例子【英文标题】:Decorator Pattern vs Inheritance with examples 【发布时间】:2012-07-24 23:19:33 【问题描述】:

我一直在尝试使用装饰器模式来扩展你不想接触的代码的功能,我知道如何实现它,但是我现在不确定你为什么不只是从原始类继承并扩展那样。

我已经读到装饰器模式允许您在运行时添加功能,而继承意味着它在编译时就在那里。

我不明白。

有人可以解释一下,提供示例并解释何时使用装饰器与继承更好。

谢谢

【问题讨论】:

继承和扩展方法都是在编译时添加函数 可能是一个很好的理解起点是专门介绍装饰器模式的***文章:en.wikipedia.org/wiki/Decorator_pattern @peer 请在答案中解释更多。我看不到装饰器如何让您在运行时做任何事情 @Jon:我已经添加了一些示例,以更实际地回答您使用可能有帮助的装饰器所做的事情。我不认为真的存在运行时/编译时间以我会这样描述的方式进行拆分,但是您可以选择将哪个装饰器应用于对象(例如用户选择),然后该对象的行为会有所不同在运行时编译器不知道它不仅仅是原始类型。我不会说它在运行时添加了功能。 :) 【参考方案1】:

假设您创建了一个 View 类,它以某种方式显示您的项目。 现在你决定你还想要一个可滚动的版本,所以你创建了一个继承 View 的 ScrollableView。 后来你决定你也想要一个有边框的版本,所以你现在需要制作一个 BorderedView 和一个 BorderdScrollableView。

另一方面,如果您可以为每个添加的样式制作一个装饰器。您将拥有以下课程:

查看 可滚动装饰器 BorderedDecorator

当你想要一个有边框的滚动视图时:

new BorderedDecorator(new ScrollableDecorator(new View()))

因此,您可以仅使用 3 个类来配置任何组合。而且您可以在运行时添加或删除它们(假设您单击一个显示添加边框的按钮,您现在用 BorderDecorator 包装您的视图......而如果您还没有继承,您需要实现这个视图类,或者您需要创建一个新的视图实例并将所有相关数据从第一个视图复制到第二个视图,这不像添加或删除包装器那么容易。

【讨论】:

我也有同样的问题。为什么我们不向View 类(或其继承类)添加两个属性ScrollableBordered?? 当然你也可以这样做,甚至可能更有意义。而且,想象一下如果你不能改变那个类来添加这些属性,因为它不是你的源代码,但确实通过虚拟方法等提供了扩展点。或者,也许你想保持一个基本对象相当普通,因为其他的东西需要使用更通用的版本。但我不会过多阅读所提供的示例,为简单起见,大多数示例都是相当人为的。【参考方案2】:

想象一个像文明这样的游戏,地图上的每个方格都可以附有各种资源(比如各种矿石、木材或石油等)。

如果您使用直接继承,则需要为每种正方形创建一个类。这样做会很麻烦

public class OilSquare 
public class OilAndGoldSquare 
public class GoldAndSilverSquare 
// etc.

装饰者模式允许混合和匹配,而无需创建严格的层次结构。所以,你应该有:

public class Square 
public class GoldDec 
public class SilverDec 
public class OilDec 

// ...

var crazyMix = new GoldDec(new SilverDec(new OilDec(new Square())));

换句话说,装饰器允许创建管道行为,管道中的每个步骤都可以与另一个步骤交换。

【讨论】:

所以您的示例中的每个类都将继承 Square 并具有一个接受 Square 对象作为参数的构造函数?然后每个类都会有额外的属性和方法?【参考方案3】:

正如其他人已经说过的,装饰器可以很好地为事物添加“选项”......好处在于您可以通过装饰器链接方法等。

想象一下,我买了一辆有真皮内饰、金属漆和超棒扰流板选项的汽车……

这三个选项有 8 种不同的组合,但使用装饰器你只需要三个额外的类。

有趣的是装饰器模式的工作方式。举个简单的例子:

public class MetallicPaint : Car

    private Car car;
    public MetallicPaint(Car wrappedCar)
    
        car = wrappedCar;
    

    public decimal Cost()
    
        return car.Cost() + 500;
    

    public string Description()
    
        return car.Description() + ", Metallic Paint";
    
    public string Speed()
    
        return car.Speed();
    
    [... pass through other methods and properties to the car object]

这不是一个完整的示例,但强调了装饰器如何与它正在装饰的对象进行交互。当然,因为它实现了汽车,所以它可以像汽车一样以其他方式使用(并通过装饰器不影响内部汽车对象的任何东西)。

当然,如果你有多个这样的装饰器,每个装饰器都嵌套了一辆汽车,这反过来又会增加它们的成本,它们的描述部分,也许扰流板会改变速度,而其他的则不会......

本质上,它允许您以比继承更模块化和更少基础的方式修改对象。装饰器应始终作为基础对象使用(在本例中为 Car),因此它们不应公开任何新方法或属性,只需稍微改变现有方法或属性的效果即可。

【讨论】:

此外,通过继承,您可能会创建大量违反 DRY 的重复代码。 是的,我认为在这种情况下,使用其指数类编号的继承显然是一个坏主意,特别是因为正如你所说的那样有很多重复。不过,我想更多地关注装饰的积极方面,而不是继承的消极方面。我喜欢你可以链接函数调用来构建东西的方式...... 我可能会尝试做一个示例应用程序来执行此操作,因为我对 IOC 方面的工作方式很感兴趣。 如果您有兴趣,我从一本名为“Head First Design Patterns”的非常好的书中学到了我所知道的关于这个(以及许多其他模式)的大部分内容。如果您想阅读很多模式,我强烈推荐它。它是针对 Java 编写的,但老实说,由于 Java 和 C# 之间的相似性,您几乎没有注意到。 :) 我认为只有 7 个选项 :)【参考方案4】:

如果您要添加许多功能并且还需要组合这些功能,则装饰器模式比继承更好。假设您的基类是 A,并且您想扩展(装饰)这个基类,其特征是 f1,f2,f3,f4 以及它们的某种组合,例如 (f1,f2) 和 (f1,f3) 和 ..;所以你需要在你的层次结构中创建 4!=4*3*2*1=24 类(每个特征 4 个,其余的用于它们的组合)。而使用装饰图案,您只需要创建 4 个类!


@Seyed Morteza Mousavi 在@Razvi 帖子中: 你是对的,我们可以将两个属性 Scrollable 和 Bordered 添加到 View 类,然后检查该属性是否设置为 true 以便运行所需的行为。但这要求我们已经知道我们需要的功能的数量(装饰器模式不是这种情况)。否则,对于我们想要添加到类中的每个新功能(例如 f1),我们需要更改我们的主类,或者继承主类(您会说)并添加属性。采用后一种方法,您将进一步需要更改处理功能组合的代码部分(这不好,因为它不遵守“松散耦合!”的经验法则)

希望这会有所帮助。

【讨论】:

以上是关于装饰器模式与继承的例子的主要内容,如果未能解决你的问题,请参考以下文章

代理模式、装饰者模式

二十三种设计模式之装饰器模式

Java 设计模式之装饰器学习与掌握

Java 设计模式之装饰器学习与掌握

设计模式之---装饰器设计模式

设计模式--装饰器模式