设计模式:建造者模式

24

我一直在寻找一个好的C#语言下的Builder模式实例,但是无法找到一个好的例子,这可能是因为我不理解Builder模式或者我试图做一些并不打算做的事情。例如,如果我有一个抽象汽车和抽象构建器方法来创建汽车部件,我应该能够将我的30个选择发送给Director,让它构建我所需要的零件,然后构建我的汽车。无论哪种汽车、卡车、半挂车等生产的产品,我都应该能够以完全相同的方式“驾驶”它。

第一个问题是,大多数示例都在具体部件中硬编码属性值,而我认为这些值应来自数据库。我认为的想法是将我的选择从数据源发送到Director,并基于我的数据创建定制的产品。

第二个问题是,我希望构建器方法实际上能够创建部件,然后将它们分配给产品,而不是传递字符串而是真正的强类型产品部件。

例如,我想通过让Builder为我制造表单字段(包括标签、输入部分和验证等)来动态创建表单。这样,我可以从ORM读取对象,查看对象的元数据,将其传递给我的Builder,并将新创建的用户控件结果添加到我的Web表单中。

然而,我找到的所有Builder示例都只有硬编码数据,而没有将选择从主代码传递给Builder并生成定制的产品。一切似乎都是一个大的静态case语句。例如,如果我有三个参数每个有10个选择,我不想构建30个具体的Builder方法,我只想创建足够制造我所需属性的部件,这可能仅需要三个。

我有意让Director仅存在于主代码中。应该有一种方法来自动确定要调用哪个具体的构建器方法,类似于多态和方法重载(尽管这是一个非常糟糕的例子),而不是在模式内使用case语句。(每次我需要添加新的产品类型时,我都需要修改现有的Director,这很糟糕)。


也许,建造者模式中缺少的是对象在结束时没有被销毁,因此我建议如果您已经理解了该模式,可以稍后添加这个功能... - Tobias
这可能不是你想要的,但请查看以下文章:<br> 模式描述<br> C# 实现示例<br> 希望能对你有所帮助。 - 0x49D1
4个回答

23

大多数情况下,Builder 模式的调用看起来像这样:

Car car = new CarBuilder().withDoors(4).withColor("red").withABS(true).build();

2
这并不是《设计模式》中所描述的建造者模式。该模式旨在创建相同源的不同表示形式。例如,编译器使用一个解析器,但对于 x86、x64 和 Java 字节码有不同的后端。 - Jonathan Allen
5
虽然可能与设计模式所描述的不完全相同,但确保创建的对象具有正确设置的所有属性。您还可以使用流畅接口来构建对象生成器...但这是另一个问题。 - Kane

14

我以前从未这样想过,但是LINQ(模式而非语法)实际上是一个构建器,对吗?

它是一种流畅的接口,用于构建查询,并可以创建不同表示形式的查询(SQL、内存中的对象查询、Web服务查询,Bart de Smet还编写了Linq-to-Excel的实现)。


11

我将参考维基百科文章这里中的C#示例。

第一个问题是,大多数示例都在具体部分中硬编码属性值,而我真的认为应该从数据库中获取数据。我的想法是将我的选择发送给Director(来自数据源),并且让建造者根据我的数据创建一个定制的产品。

在这种情况下,您可以使用实现PizzaBuilder的类,该类知道如何从数据库检索数据。你可以有几种方法来做到这一点。

一种方法是创建一个HawaiianPizzaBuilder。当类初始化时,它会查询数据库以获取夏威夷比萨,并检索行。然后,在调用各种Build(x)方法时,它会将属性设置为检索到的数据库行的相应字段。

另一种方法就是创建一个PizzaDatabaseBuilder,并确保在初始化类时,将需要的pizza类型的行ID传递给它。例如,不是

waiter.PizzaBuilder = new HawaiianPizzaBuilder();
您使用
waiter.PizzaBuilder = new PizzaDatabaseBuilder("Hawaiian");

第二个问题是我希望构建器方法实际上创建部件,然后将它们分配给产品,而不是传递字符串,而是真正的强类型产品部件。

这应该不是问题。你需要的是另一种工厂/构建器类型模式来初始化披萨的字段。例如:

而不是

 public override void BuildDough()   { pizza.Dough   = "pan baked"; }

你可以这样做

 public override void BuildDough()   { pizza.Dough   = new DoughBuilder("pan baked"); }
或者
 public override void BuildDough()   { pizza.Dough   = new PanBakedDoughBuilder(); }

DoughBuilder可以去你的数据库中的另一张表,以正确填写PizzaDough类。


好的答案。如果我们使用静态声明的工厂类,就不需要使用new运算符。Tobiask的构建器调用也很棒,就像jQuery一样。我从中得出的结论是,当你不需要一个director时,这两种方法都可以。关键在于实际编码。非常感谢。 - Zachary Scott
你越理解这个答案,就越能意识到RS Conley的深厚知识。 :) 如果可以的话,我会给100分。 - Zachary Scott

0

我认为你不能避免这两个问题 - 部件过载较少和堆栈中某处有case/if语句。当添加新类时,修改代码可能是唯一的选择。

话虽如此,你可以通过其他模式获得帮助 - 即工厂模式可以帮助你进行构建。合理使用多态性(例如,所有部件都继承自某种类型,无论是类还是接口)可以减少if/case和过载的数量。

希望这能帮到你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接