Answers:
构建器和工厂恕我直言之间的主要区别在于,当您需要做很多事情来构建对象时,构建器非常有用。例如,想象一个DOM。您必须创建大量节点和属性才能获得最终对象。当工厂可以在一个方法调用中轻松创建整个对象时,将使用工厂。
使用构建器的一个示例是构建XML文档,例如在构建HTML片段时就使用了此模型,例如,我可能具有用于构建特定类型表的构建器,并且可能具有以下方法(未显示参数):
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
然后,该构建器将为我吐出HTML。这比遍历大型过程方法更容易阅读。
以下是在Java中使用模式和示例代码的一些原因,但这是在设计模式中由四人组成的Builder模式的实现。在Java中使用它的原因也适用于其他编程语言。
如Joshua Bloch在《有效Java,第二版》中所述:
当设计其构造函数或静态工厂将具有多个参数的类时,构造器模式是一个不错的选择。
在某个时候,我们所有人都遇到了一个带有构造函数列表的类,其中每个添加项都会添加一个新的option参数:
Pizza(int size) { ... }
Pizza(int size, boolean cheese) { ... }
Pizza(int size, boolean cheese, boolean pepperoni) { ... }
Pizza(int size, boolean cheese, boolean pepperoni, boolean bacon) { ... }
这称为伸缩构造函数模式。这种模式的问题在于,一旦构造函数具有4或5个参数,就很难记住参数的必需顺序以及在给定情况下可能需要的特定构造函数。
伸缩构造器模式的一种替代方法是JavaBean模式,您可以在其中调用带有必需参数的构造器,然后在之后调用任何可选的setter:
Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
这里的问题是,由于对象是通过多个调用创建的,因此在其构造过程中可能处于不一致状态。这也需要付出很多额外的努力来确保线程安全。
更好的选择是使用构建器模式。
public class Pizza {
private int size;
private boolean cheese;
private boolean pepperoni;
private boolean bacon;
public static class Builder {
//required
private final int size;
//optional
private boolean cheese = false;
private boolean pepperoni = false;
private boolean bacon = false;
public Builder(int size) {
this.size = size;
}
public Builder cheese(boolean value) {
cheese = value;
return this;
}
public Builder pepperoni(boolean value) {
pepperoni = value;
return this;
}
public Builder bacon(boolean value) {
bacon = value;
return this;
}
public Pizza build() {
return new Pizza(this);
}
}
private Pizza(Builder builder) {
size = builder.size;
cheese = builder.cheese;
pepperoni = builder.pepperoni;
bacon = builder.bacon;
}
}
请注意,Pizza是不可变的,并且参数值都位于单个位置。因为Builder的setter方法返回了Builder对象,所以它们可以被链接。
Pizza pizza = new Pizza.Builder(12)
.cheese(true)
.pepperoni(true)
.bacon(true)
.build();
这样就产生了易于编写,非常易于阅读和理解的代码。在此示例中,可以将构建方法修改为在将参数从构建器复制到Pizza对象后检查参数,如果提供了无效的参数值,则抛出IllegalStateException。这种模式非常灵活,将来很容易向其添加更多参数。仅当要为构造函数使用4个或5个以上的参数时,它才真正有用。就是说,如果您怀疑将来可能会添加更多参数,那么首先值得这样做。
我从Joshua Bloch 的《Effective Java,第二版》一书中大量借用了这个主题。要了解有关此模式和其他有效Java实践的更多信息,我强烈建议您使用它。
new Pizza.Builder(12).cheese().pepperoni().bacon().build();
Pizza.Builder(12).cheese().pepperoni().bacon().build();
,如果您只需要一些意大利辣味香肠,则需要重新编译代码或使用不必要的逻辑披萨。至少您还应该提供参数化版本,例如最初建议的@Kamikaze Mercenary。Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();
。再说一次,我们从不进行单元测试,对吗?
考虑一家餐厅。“今天的饭”的创建是一种工厂模式,因为您告诉厨房“给我今天的饭”,然后厨房(工厂)根据隐藏的标准决定要生成的对象。
如果您订购自定义披萨,则会显示该构建器。在这种情况下,服务员告诉厨师(建造者):“我需要一个比萨饼;在其中添加奶酪,洋葱和培根!” 因此,构建器公开了所生成对象应具有的属性,但隐藏了如何设置它们。
.NET StringBuilder类是构建器模式的一个很好的例子。它主要用于通过一系列步骤来创建字符串。执行ToString()的最终结果始终是字符串,但是该字符串的创建会根据使用StringBuilder类中的函数而有所不同。综上所述,基本思想是构建复杂对象并隐藏其构建方式的实现细节。
b.append(...).append(...)
在最终调用之前可以进行链接toString()
。引文: infoq.com/articles/internal-dsls-java
当您有很多选项要处理时,可以使用它。考虑一下jmock之类的事情:
m.expects(once())
.method("testMethod")
.with(eq(1), eq(2))
.returns("someResponse");
感觉自然得多,是...可能的。
还有xml构建,字符串构建和许多其他功能。想象一下,如果java.util.Map
把它当做建造者。您可以执行以下操作:
Map<String, Integer> m = new HashMap<String, Integer>()
.put("a", 1)
.put("b", 2)
.put("c", 3);
通过Microsoft MVC框架时,我想到了构建器模式。我在ControllerBuilder类中遇到了这种模式。此类将返回控制器工厂类,然后将其用于构建具体的控制器。
我在使用构建器模式时看到的优势是,您可以创建自己的工厂并将其插入框架。
@Tetha,可能有一家意大利人经营的餐厅(框架),提供比萨饼。为了准备比萨饼,意大利佬(Object Builder)使用带有比萨饼基础层的Owen(Factory)。
现在,印度人接管了意大利人的餐厅。印度餐厅(框架)服务器使用dosa代替披萨。为了准备dosa印度小伙子(对象生成器)使用带有Maida(基类)的煎锅(工厂)
如果您看场景,食物是不同的,食物的准备方式是不同的,但是在同一家餐厅(在同一框架下)。餐厅的建造方式应能支持中国,墨西哥或其他美食。框架内的对象生成器有助于插入所需的菜式。例如
class RestaurantObjectBuilder
{
IFactory _factory = new DefaultFoodFactory();
//This can be used when you want to plugin the
public void SetFoodFactory(IFactory customFactory)
{
_factory = customFactory;
}
public IFactory GetFoodFactory()
{
return _factory;
}
}
我始终不喜欢Builder模式,因为它笨拙,笨拙并且经常被经验不足的程序员滥用。它是一种模式,仅在您需要从需要后初始化步骤的某些数据中组装对象时才有意义(即,一旦收集了所有数据,请对其进行处理)。取而代之的是,在99%的时间内,仅使用构建器来初始化类成员。
在这种情况下,最好只withXyz(...)
在类中声明类型设置器,并使它们返回对自身的引用。
考虑一下:
public class Complex {
private String first;
private String second;
private String third;
public String getFirst(){
return first;
}
public void setFirst(String first){
this.first=first;
}
...
public Complex withFirst(String first){
this.first=first;
return this;
}
public Complex withSecond(String second){
this.second=second;
return this;
}
public Complex withThird(String third){
this.third=third;
return this;
}
}
Complex complex = new Complex()
.withFirst("first value")
.withSecond("second value")
.withThird("third value");
现在,我们有了一个简洁的单一类,该类可以管理自己的初始化,并且与生成器几乎一样的工作,除了它的优雅得多。
构建器的另一个优点是,如果您拥有一个Factory,那么代码中仍然存在一些耦合,因为要使Factory工作,它必须知道它可能创建的所有对象。如果添加了另一个可以创建的对象,则必须修改工厂类以使其包括在内。这也发生在抽象工厂中。
另一方面,使用构建器,您只需要为此新类创建一个新的具体构建器。Director类将保持不变,因为它在构造函数中接收了构造器。
此外,还有很多建筑商的口味。神风雇佣兵给出了另一个。
/// <summary>
/// Builder
/// </summary>
public interface IWebRequestBuilder
{
IWebRequestBuilder BuildHost(string host);
IWebRequestBuilder BuildPort(int port);
IWebRequestBuilder BuildPath(string path);
IWebRequestBuilder BuildQuery(string query);
IWebRequestBuilder BuildScheme(string scheme);
IWebRequestBuilder BuildTimeout(int timeout);
WebRequest Build();
}
/// <summary>
/// ConcreteBuilder #1
/// </summary>
public class HttpWebRequestBuilder : IWebRequestBuilder
{
private string _host;
private string _path = string.Empty;
private string _query = string.Empty;
private string _scheme = "http";
private int _port = 80;
private int _timeout = -1;
public IWebRequestBuilder BuildHost(string host)
{
_host = host;
return this;
}
public IWebRequestBuilder BuildPort(int port)
{
_port = port;
return this;
}
public IWebRequestBuilder BuildPath(string path)
{
_path = path;
return this;
}
public IWebRequestBuilder BuildQuery(string query)
{
_query = query;
return this;
}
public IWebRequestBuilder BuildScheme(string scheme)
{
_scheme = scheme;
return this;
}
public IWebRequestBuilder BuildTimeout(int timeout)
{
_timeout = timeout;
return this;
}
protected virtual void BeforeBuild(HttpWebRequest httpWebRequest) {
}
public WebRequest Build()
{
var uri = _scheme + "://" + _host + ":" + _port + "/" + _path + "?" + _query;
var httpWebRequest = WebRequest.CreateHttp(uri);
httpWebRequest.Timeout = _timeout;
BeforeBuild(httpWebRequest);
return httpWebRequest;
}
}
/// <summary>
/// ConcreteBuilder #2
/// </summary>
public class ProxyHttpWebRequestBuilder : HttpWebRequestBuilder
{
private string _proxy = null;
public ProxyHttpWebRequestBuilder(string proxy)
{
_proxy = proxy;
}
protected override void BeforeBuild(HttpWebRequest httpWebRequest)
{
httpWebRequest.Proxy = new WebProxy(_proxy);
}
}
/// <summary>
/// Director
/// </summary>
public class SearchRequest
{
private IWebRequestBuilder _requestBuilder;
public SearchRequest(IWebRequestBuilder requestBuilder)
{
_requestBuilder = requestBuilder;
}
public WebRequest Construct(string searchQuery)
{
return _requestBuilder
.BuildHost("ajax.googleapis.com")
.BuildPort(80)
.BuildPath("ajax/services/search/web")
.BuildQuery("v=1.0&q=" + HttpUtility.UrlEncode(searchQuery))
.BuildScheme("http")
.BuildTimeout(-1)
.Build();
}
public string GetResults(string searchQuery) {
var request = Construct(searchQuery);
var resp = request.GetResponse();
using (StreamReader stream = new StreamReader(resp.GetResponseStream()))
{
return stream.ReadToEnd();
}
}
}
class Program
{
/// <summary>
/// Inside both requests the same SearchRequest.Construct(string) method is used.
/// But finally different HttpWebRequest objects are built.
/// </summary>
static void Main(string[] args)
{
var request1 = new SearchRequest(new HttpWebRequestBuilder());
var results1 = request1.GetResults("IBM");
Console.WriteLine(results1);
var request2 = new SearchRequest(new ProxyHttpWebRequestBuilder("localhost:80"));
var results2 = request2.GetResults("IBM");
Console.WriteLine(results2);
}
}
在先前的答案(双关语意味深长)的基础上,Groovy内置了对的支持,这是一个出色的真实示例Builders
。
签出InnerBuilder,这是一个IntelliJ IDEA插件,它向“生成”菜单(Alt + Insert)添加了一个“ Builder”操作,该菜单生成有效的Java中所述的内部生成器类。
当我想为我的XML使用标准XMLGregorianCalendar来反对Java中的DateTime编组时,我听到了很多关于使用它的繁重和繁琐的评论。我试图在xs:datetime结构中转换XML字段,以管理时区,毫秒等。
因此,我设计了一个实用程序,用于从GregorianCalendar或java.util.Date构建XMLGregorian日历。
由于我在哪里工作,我无法不合法地在线共享它,但这是客户如何使用它的示例。它抽象了细节并过滤了XMLGregorianCalendar的一些实现,这些实现较少用于xs:datetime。
XMLGregorianCalendarBuilder builder = XMLGregorianCalendarBuilder.newInstance(jdkDate);
XMLGregorianCalendar xmlCalendar = builder.excludeMillis().excludeOffset().build();
授予此模式更多的是过滤器,因为它将xmlCalendar中的字段设置为未定义,因此将其排除在外,它仍然“构建”它。我已经轻松地向构建器添加了其他选项,以创建xs:date和xs:time结构,并在需要时操纵时区偏移量。
如果您曾经看过创建和使用XMLGregorianCalendar的代码,那么您将看到它如何使它变得更容易操作。
在单元测试类时,可以使用一个很好的现实世界示例。您使用sut(受测系统)构建器。
例:
类:
public class CustomAuthenticationService
{
private ICloudService _cloudService;
private IDatabaseService _databaseService;
public CustomAuthenticationService(ICloudService cloudService, IDatabaseService databaseService)
{
_cloudService = cloudService;
_databaseService = databaseService;
}
public bool IsAuthorized(User user)
{
//Implementation Details
return true;
}
测试:
[Test]
public void Given_a_User_With_Permission_When_Verifying_If_Authorized_Then_Authorize_It_Returning_True()
{
CustomAuthenticationService sut = new CustomAuthenticationServiceBuilder();
User userWithAuthorization = null;
var result = sut.IsAuthorized(userWithAuthorization);
Assert.That(result, Is.True);
}
sut Builder:
public class CustomAuthenticationServiceBuilder
{
private ICloudService _cloudService;
private IDatabaseService _databaseService;
public CustomAuthenticationServiceBuilder()
{
_cloudService = new AwsService();
_databaseService = new SqlServerService();
}
public CustomAuthenticationServiceBuilder WithAzureService(AzureService azureService)
{
_cloudService = azureService;
return this;
}
public CustomAuthenticationServiceBuilder WithOracleService(OracleService oracleService)
{
_databaseService = oracleService;
return this;
}
public CustomAuthenticationService Build()
{
return new CustomAuthenticationService(_cloudService, _databaseService);
}
public static implicit operator CustomAuthenticationService (CustomAuthenticationServiceBuilder builder)
{
return builder.Build();
}
}
CustomAuthenticationService
类中添加setter ?