你什么时候会使用建造者模式?[关闭]
-
11-07-2019 - |
题
有哪些 常见的, 现实世界的例子 使用构建器模式?它给你买了什么?为什么不直接使用工厂模式呢?
解决方案
建筑工地和工厂恕我直言之间的主要区别,就是当你需要做很多事情来构建对象的建设者是非常有用的。例如想象一个DOM。你必须创造大量的节点和属性,让您的最终目标。工厂用于在出厂时可以很容易地创建一个方法调用中的整个对象。
使用的助洗剂的一个例子是一个建筑物的XML文档,我已经构建HTML片段例如当使用该模型可能我生成器用于构建特定类型的表中的,它可能有以下几种方法的 (参数未示出)强>:
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
这建设者将然后吐出的HTML我。这是更容易使用比通过大量的程序方法行走来阅读。
查核维基百科上的生成器模式。
其他提示
下面是一些争论在 Java 中使用该模式和示例代码的原因,但它是四人帮所涵盖的构建器模式的实现 设计模式. 。在 Java 中使用它的原因也适用于其他编程语言。
正如约书亚·布洛赫 (Joshua Bloch) 在《 有效的 Java,第二版:
当设计其构造函数或静态工厂具有多个参数的类时,构建器模式是一个不错的选择。
我们都曾在某个时候遇到过一个带有构造函数列表的类,其中每次添加都会添加一个新的选项参数:
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模式 您可以在其中使用强制参数调用构造函数,然后在之后调用任何可选设置器:
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 个时,它才真正有用。也就是说,首先这可能是值得的 如果您怀疑将来可能会添加更多参数。
我从书中大量借用了关于这个主题的内容 有效的 Java,第二版 约书亚·布洛赫着。了解有关此模式和其他有效 Java 实践的更多信息 我强烈推荐它。
考虑的餐厅。 “今天的饭”的创建是一个工厂模式,因为你告诉厨房“让我今天的饭”,厨房(工厂)决定什么对象,生成的基础上,隐藏的标准。
如果您订购定制的比萨饼出现建设者。在这种情况下,服务员告诉厨师(制造商)“我需要一个比萨饼;!加奶酪,洋葱和腊肉给它”因此,助洗剂公开所生成的对象应具有的属性,但隐藏如何设置它们。
.NET StringBuilder类是助洗剂图案的一个很好的例子。它主要用于创建一系列步骤的字符串。你做的ToString()的最终结果始终是一个字符串,但该字符串的建立根据使用了什么样的StringBuilder类的功能而异。归纳起来,其基本思想是建立复杂的物体和隐藏它是如何正在修建的实施细节。
有关多线程问题,我们需要建立到每个线程一个复杂的对象。该对象表示正在处理的数据,并且可以根据用户输入而改变。
难道我们使用一个工厂呢?是
为什么我们没有?生成器更有意义,我猜。
工厂用于创建不同类型的有相同的基本类型(实现相同的接口或基类)的对象。
助洗剂反复建立同一类型的对象,但施工动态,因此它可以在运行时改变。
虽然经历微软的MVC框架,我有一个关于建造者模式的思想。我在为ControllerBuilder类跨越格局来了。这个类是返回控制器工厂类,然后将其用于建造混凝土控制器。
我的优势在使用生成器模式看到的是,你可以创建一个工厂自己的,并将其插入到框架中。
@Tetha,可以有由意大利人经营餐馆(框架),该供应比萨饼。为了制备意大利比萨人(对象生成器)使用欧文(厂)与比萨饼碱(碱类)。
现在印度的家伙接管来自意大利帅哥的餐厅。印度餐厅(框架)服务器DOSA而不是比萨饼。为了制备DOSA印度人(对象构建器)使用煎锅(厂)与瑞梅达(基类)
如果你看一下情况,食物是不同的,这样准备食物不同,但在同一家餐馆(同一框架下)。餐厅应建立在这样一种方式,它可以支持中国,墨西哥或任何美食。内部框架对象Builder简化到插件那种你想要的美食。例如
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;
}
}
当你有很多的选择来处理您可以使用它。认为这样的事情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);
基于之前的答案(双关语),一个很好的现实例子是 格罗维内置支持 Builders
.
建设者的另一个优点是,如果你有一个工厂,还有在你代码中的一些耦合,因为作为工厂的工作,它必须知道一切都不可能创建对象 。如果添加的是可以创造另外一个对象,你将不得不修改工厂类,包括他。这发生在抽象工厂以及
在与建筑商,在另一方面,你只需要创建一个新的混凝土建造者为这个新类。导演类将保持不变,因为它接收在构造函数中的助洗剂。
此外,还有助洗剂的许多香料。神风Mercenary`s给另一个。
/// <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);
}
}
我一直不喜欢Builder模式的东西不实用,突兀,而且往往由缺乏经验的程序员滥用。它是一种模式,如果你需要从一些数据需要组装对象,它才有意义一个初始化后步(即一旦所有的数据收集 - 用它做的东西)。取而代之的是,在时间助洗剂的99%被简单地用于初始化类成员。
在这种情况下,它是好得多简单地声明类内部withXyz(...)
类型setter和使它们的基准返回到自身。
考虑这样的:
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");
现在我们有一个管理自己的初始化和做几乎同样的工作作为建设者,除了它的更优雅简洁的单一类。
我以前在本土消息传递库助洗剂。库芯从线接收数据,具有生成器实例收集它,然后,一旦生成器决定it've得到它创建一个消息实例所需的一切,Builder.GetMessage()已构建使用来自所收集的数据的消息实例丝。
当我想用标准的XMLGregorianCalendar我的XML到对象的DateTime的编组在Java中,我听到了那是多么沉重的重量和使用繁琐了很多的意见。我试图comtrol的XML领域中的xs:日期时间结构来管理时区,毫秒,等等
因此,我设计了一个实用程序从一个GregorianCalendar或java.util.Date构建XMLGregorian日历。
由于我工作的地方我不能在网上分享其没有法律,但这里的客户端如何使用它的一个例子。它抽象的细节和过滤某些为XS较少使用的XMLGregorianCalendar的执行情况:日期时间
XMLGregorianCalendarBuilder builder = XMLGregorianCalendarBuilder.newInstance(jdkDate);
XMLGregorianCalendar xmlCalendar = builder.excludeMillis().excludeOffset().build();
授予该模式是多个过滤器,因为它设置在xmlCalendar为未定义字段,以便它们被排除在外,它仍然是“建立”它。我已经很容易地添加其他选项到生成器来创建一个xs:日期,和xs:在需要的时候时间struct和还操纵时区偏移
如果你曾经看到,创建和使用的XMLGregorianCalendar的代码,你会看到这是如何使人们更容易操纵。
查核InnerBuilder,一个IntelliJ IDEA的插件,增加了一个“生成器”作用于生成菜单(ALT +插入)在有效的Java描述,其生成内部生成器类
有一个伟大的现实世界的例子是,当单元测试你的类使用。您可以使用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生成器:
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();
}
}