建造者与装饰者模式[关闭]
Posted
技术标签:
【中文标题】建造者与装饰者模式[关闭]【英文标题】:Builder Vs Decorator pattern [closed] 【发布时间】:2011-06-13 16:36:28 【问题描述】:来自When would you use the Builder Pattern?,
据说builder模式适合Pizza的例子。
为什么不是装饰器? 将奶酪、意大利辣香肠、培根作为基础披萨的额外装饰。
是不是因为奶酪/意大利辣香肠必须分开建造?我不认为,它们需要单独构建,因为它们可以现成可用。
请澄清。 我也在寻找装饰器模式的一个很好的真实世界示例,以及为什么它适合该特定示例的原因。谢谢。
【问题讨论】:
当我通过示例了解到这一点时,我也有同样的想法。教授帮不了我,但确实可以,谢谢! 【参考方案1】:来自***的装饰器模式文章:
在面向对象编程中, 装饰器模式是一种设计模式 允许新的/额外的行为 添加到现有对象 动态的。
披萨完全制作完成后,无需添加配料。你不会吃半个比萨饼,然后再添加一个配料。
换句话说,构建器模式可以很容易地构造一个可在在构造时以独立方向扩展的对象,而装饰器模式可以让您向对象添加功能扩展施工时间之后。使用装饰器模式来构造对象是不好的,因为它使对象处于不一致(或至少不正确)的状态,直到所有必需的装饰器都到位 - 类似于使用 setter 指定可选构造函数参数的 JavaBean 问题。
【讨论】:
potter:实际上,一些配料,如番茄酱、辣椒片,会与披萨一起提供。那么我们可以说我们应用了这两种模式吗?第一个建造者,然后是装饰者? potter: +1,我上面关于浇头的假设似乎是正确的,来自***.com/questions/2707401/… @bjs:是的,装饰器和构建器可以合理地结合起来,你的例子是一种可能性;但是,它会导致设计复杂且代码可读性较差。如果我确定项目确实需要它,而不仅仅是我的“模式发烧”案例,我只会使用 Builder + Decorator。考虑是否值得只向基类添加一个addChilli()
方法而不是构建一个完整的ChilliDecorator
。 (一如既往,没有正确答案。)
构建器是一种将装饰器应用于对象的合理方式。您当然可以使用构建器来装饰披萨并从构建器返回完全装饰的披萨对象。我想说这两种模式有一种携手合作的习惯。
除了@PhilipPotter 所说的,如果您使用构建器模式,您将只能在编译时执行此操作,因为您需要确切知道在构建器链中调用哪些方法。 (例如:pizza.Builder().withCheese().withOnions().build()
)。将此与装饰器模式进行比较,您可以使用工厂根据运行时输入来装饰您的披萨(例如:factory.createPizza(String []toppings)
)。在工厂内部,您可以迭代浇头并装饰披萨(例如:if("cheese".equals(toppings[i]) pizza = new PizzaWithCheese(pizza)
)【参考方案2】:
你混淆了两个非常不同的东西。 GoF 将 Builder 归类为创建模式,而 Decorator 归类为结构模式。它们描述如下(Gamma 等人,第 1 页):
Builder (97) 将复杂对象的构造与其表示分离,以便相同的构造过程可以创建不同的表示。
装饰器 (175) 动态地将附加职责附加到对象。 装饰器为扩展功能提供了一种灵活的替代子类的方法。
注意对装饰器的强调。它是子类化的灵活替代方案。子类化用于建模 is-a 关系。奶酪不是披萨。比萨饼由多种成分组成,通常使用复合来建模。
建造者模式在这里是相关的,因为有如此大量的成分,需要以标准化的方式构建它们。
举一个装饰器的真实示例,我最近想在我的 java 应用程序中记录使用 jdbc 执行的查询。我通过实现一个名为 LoggingConnection 的类来实现这一点,该类扩展了 Connection 接口。
public class LoggingConnection implements Connection
public static class LogEntry
public String sql;
public int invocationCount;
public double avgTime;
public double maxTime;
private Connection delegate;
private Map<String, LogEntry> log;
public LoggingConnection(Connection delegate)
this.delegate = delegate;
this.log = new HashMap<String, LogEntry>();
public Map<String, LogEntry> getLog()
return log;
@Override
public void clearWarnings()
throws SQLException
delegate.clearWarnings();
@Override
public void close()
throws SQLException
delegate.close();
// forwarding declarations to all other methods declared in the interface
...
这允许我传递连接的具体实现,并在运行时扩展其功能。在这种情况下,子类化将是有问题的,因为您不一定知道实际返回的连接对象。这是因为它是使用 DriverManager 工厂为您构建的:
Connection conn = DriverManger.getConnection(dsn);
conn 对象在这种情况下是包含在驱动程序中的一个实现,我通常不知道它的名称。装饰器方法的优点是我不必知道,而且它不依赖于特定的实现。
【讨论】:
好吧,“替代方案”表明也可以使用子类化。子类化的替代方法不是让奶酪成为披萨的子类,而是让奶酪 pizza 成为披萨的子类。因为奶酪披萨就是披萨。 @Lèse:但是可以想象,您可以实现一个装饰器,它可以像子类一样添加相同的“奶酪”功能。唯一的主要区别是您可以决定在运行时进行扩展。 @Emil:对,在这种情况下,装饰器仍然是一种更好、更灵活的模式。如果你要走子类化路线,我只是指着等价物。 @Lèse:好的。有道理。 :) 装饰器实现了比萨饼及其增量。构建器构建实现链。它们是完全独立的关注点。这两个部分可以在没有另一个的情况下存在......构建器并不需要知道实现是使用装饰器还是只是一种组合。【参考方案3】:让我们来看看 Builder 和 Decorator 的关键特性。
Builder:(创造模式)
-
从客户端程序传递到 Factory 类的参数过多,容易出错
某些参数可能是可选的,不像 Factory 中强制发送所有参数
对象很重,它的创建很复杂。例如制作各种类型的比萨饼
Decorator:(一种结构模式)
-
在运行时向对象添加行为。继承是实现这一功能的关键,这既是这种模式的优点也是缺点。
它增强了界面的行为。
Decorator 可以被视为只有一个组件的退化组合。但是,装饰器增加了额外的职责 - 它不适合对象聚合。
装饰器支持递归组合
装饰器旨在让您无需子类化即可向对象添加职责
什么时候使用装饰器:
-
应动态添加/删除对象职责和行为
具体实施应与职责和行为分离
当子类化成本太高而无法动态添加/删除职责时
回到您的查询:
Builder 是 Pizza 的正确创建模式。披萨最初是用强制性成分(面包等)制作的。奶酪、意大利辣香肠、培根是可选成分,但它们仍然可以在制作过程中成为比萨饼的一部分。
装饰器 对于在运行时为已创建的对象添加动态职责非常有用。
例如:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("a.txt")));
更多详情请参考以下帖子:
Keeping builder in separate class (fluent interface)
When to Use the Decorator Pattern?
【讨论】:
【参考方案4】:构建器模式专门用于构建和装饰器在构建后添加特殊功能。 例如,在上面的 Pizza 示例中,我们可以根据问题域决定使用两种模式之一。
如果辣椒片对于披萨来说是必不可少的,并且类似地,我们有很多成分要添加到披萨中,其中很少有基本的成分可以使披萨可食用(有意义的状态),我们可以更喜欢使用 Builder。 披萨制作完成后,我们可以稍后用番茄酱、橄榄等不同的配料装饰它......
此外,上面正确地说明了它们可以一起使用,但这会增加复杂性。所以我们应该只在问题领域需要时才明智地使用模式,否则一个简单的构造就足够了。
【讨论】:
以上是关于建造者与装饰者模式[关闭]的主要内容,如果未能解决你的问题,请参考以下文章