Một số là gì phổ biến , ví dụ thế giới thực của việc sử dụng các mẫu Builder? Nó mua gì cho bạn? Tại sao không sử dụng Mô hình nhà máy?
Một số là gì phổ biến , ví dụ thế giới thực của việc sử dụng các mẫu Builder? Nó mua gì cho bạn? Tại sao không sử dụng Mô hình nhà máy?
Câu trả lời:
Sự khác biệt chính giữa nhà xây dựng và nhà máy IMHO, là công cụ xây dựng rất hữu ích khi bạn cần làm nhiều việc để xây dựng một đối tượng. Ví dụ, hãy tưởng tượng một DOM. Bạn phải tạo nhiều nút và thuộc tính để có được đối tượng cuối cùng. Một nhà máy được sử dụng khi nhà máy có thể dễ dàng tạo toàn bộ đối tượng trong một cuộc gọi phương thức.
Một ví dụ về việc sử dụng trình xây dựng là xây dựng tài liệu XML, tôi đã sử dụng mô hình này khi xây dựng các đoạn HTML, ví dụ tôi có thể có Trình tạo để xây dựng một loại bảng cụ thể và nó có thể có các phương thức sau (tham số không được hiển thị) :
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
Trình xây dựng này sau đó sẽ nhổ HTML cho tôi. Điều này dễ đọc hơn nhiều so với việc đi qua một phương pháp thủ tục lớn.
Kiểm tra Builder Model trên Wikipedia .
Dưới đây là một số lý do để tranh luận về việc sử dụng mẫu và mã ví dụ trong Java, nhưng đó là một triển khai của Mẫu xây dựng được bao phủ bởi Gang of Four trong các mẫu thiết kế . Những lý do bạn sẽ sử dụng nó trong Java cũng có thể áp dụng cho các ngôn ngữ lập trình khác.
Như Joshua Bloch tuyên bố trong Java hiệu quả, Phiên bản 2 :
Mẫu trình xây dựng là một lựa chọn tốt khi thiết kế các lớp có các nhà xây dựng hoặc nhà máy tĩnh sẽ có nhiều hơn một số tham số.
Tại một số thời điểm, tất cả chúng ta đã gặp một lớp với một danh sách các hàm tạo trong đó mỗi phép cộng thêm một tham số tùy chọn mới:
Pizza(int size) { ... }
Pizza(int size, boolean cheese) { ... }
Pizza(int size, boolean cheese, boolean pepperoni) { ... }
Pizza(int size, boolean cheese, boolean pepperoni, boolean bacon) { ... }
Đây được gọi là Mô hình xây dựng kính thiên văn. Vấn đề với mẫu này là một khi các hàm tạo có 4 hoặc 5 tham số dài, sẽ khó nhớ thứ tự cần thiết của các tham số cũng như hàm tạo cụ thể mà bạn có thể muốn trong một tình huống cụ thể.
Một lựa chọn khác mà bạn có đối với Mẫu Trình xây dựng Kính thiên văn là Mẫu JavaBean nơi bạn gọi một hàm tạo với các tham số bắt buộc và sau đó gọi bất kỳ setters tùy chọn nào sau:
Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
Vấn đề ở đây là bởi vì đối tượng được tạo ra qua nhiều cuộc gọi nên nó có thể ở trạng thái không nhất quán giữa quá trình xây dựng. Điều này cũng đòi hỏi rất nhiều nỗ lực để đảm bảo an toàn chủ đề.
Thay thế tốt hơn là sử dụng Mô hình Builder.
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;
}
}
Lưu ý rằng Pizza là bất biến và các giá trị tham số đều nằm trong một vị trí . Bởi vì các phương thức setter của Trình xây dựng trả về đối tượng Builder, chúng có thể được nối kết .
Pizza pizza = new Pizza.Builder(12)
.cheese(true)
.pepperoni(true)
.bacon(true)
.build();
Điều này dẫn đến mã dễ viết và rất dễ đọc và dễ hiểu. Trong ví dụ này, phương thức xây dựng có thể được sửa đổi để kiểm tra các tham số sau khi chúng được sao chép từ trình tạo vào đối tượng Pizza và ném IllegalStateException nếu giá trị tham số không hợp lệ được cung cấp. Mẫu này rất linh hoạt và thật dễ dàng để thêm nhiều tham số cho nó trong tương lai. Nó thực sự chỉ hữu ích nếu bạn sẽ có nhiều hơn 4 hoặc 5 tham số cho một hàm tạo. Điều đó nói rằng, nó có thể có giá trị ở nơi đầu tiên nếu bạn nghi ngờ bạn có thể sẽ thêm nhiều tham số trong tương lai.
Tôi đã mượn rất nhiều về chủ đề này từ cuốn sách Java hiệu quả, tái bản lần thứ 2 của Joshua Bloch. Để tìm hiểu thêm về mẫu này và các thực tiễn Java hiệu quả khác, tôi đánh giá cao nó.
new Pizza.Builder(12).cheese().pepperoni().bacon().build();
Pizza.Builder(12).cheese().pepperoni().bacon().build();
, bạn sẽ cần biên dịch lại mã của mình hoặc có logic không cần thiết nếu bạn chỉ cần một số pepperoni pizza. Ít nhất, bạn cũng nên cung cấp các phiên bản tham số như @Kamikaze Mercenary đề xuất ban đầu. Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();
. Sau đó, một lần nữa, chúng tôi không bao giờ kiểm tra đơn vị, phải không?
Hãy xem xét một nhà hàng. Việc tạo ra "bữa ăn hôm nay" là một mô hình nhà máy, bởi vì bạn nói với nhà bếp "hãy cho tôi bữa ăn hôm nay" và nhà bếp (nhà máy) quyết định đối tượng nào sẽ tạo ra, dựa trên các tiêu chí ẩn.
Trình xây dựng xuất hiện nếu bạn đặt một chiếc bánh pizza tùy chỉnh. Trong trường hợp này, người phục vụ nói với đầu bếp (người xây dựng) "Tôi cần một chiếc bánh pizza, thêm phô mai, hành tây và thịt xông khói vào nó!" Do đó, trình xây dựng hiển thị các thuộc tính mà đối tượng được tạo nên có, nhưng ẩn cách đặt chúng.
Lớp .NET StringBuilder là một ví dụ tuyệt vời về mẫu trình xây dựng. Nó chủ yếu được sử dụng để tạo ra một chuỗi trong một loạt các bước. Kết quả cuối cùng bạn nhận được khi thực hiện ToString () luôn là một chuỗi nhưng việc tạo chuỗi đó thay đổi tùy theo chức năng nào trong lớp StringBuilder đã được sử dụng. Tóm lại, ý tưởng cơ bản là xây dựng các đối tượng phức tạp và ẩn các chi tiết triển khai về cách nó được xây dựng.
b.append(...).append(...)
trước khi gọi toString()
. Trích dẫn: infoq.com/articles/iternal-dsls-java
Đối với một vấn đề đa luồng, chúng tôi cần một đối tượng phức tạp được xây dựng cho mỗi luồng. Đối tượng thể hiện dữ liệu đang được xử lý và có thể thay đổi tùy thuộc vào đầu vào của người dùng.
Chúng ta có thể sử dụng một nhà máy thay thế? Đúng
Tại sao chúng ta không? Builder có ý nghĩa hơn tôi đoán.
Các nhà máy được sử dụng để tạo các loại đối tượng khác nhau có cùng loại cơ bản (thực hiện cùng một giao diện hoặc lớp cơ sở).
Các nhà xây dựng xây dựng cùng một loại đối tượng nhiều lần, nhưng việc xây dựng là động nên có thể thay đổi khi chạy.
Bạn sử dụng nó khi bạn có nhiều lựa chọn để giải quyết. Hãy suy nghĩ về những thứ như jmock:
m.expects(once())
.method("testMethod")
.with(eq(1), eq(2))
.returns("someResponse");
Nó cảm thấy tự nhiên hơn rất nhiều và ... có thể.
Ngoài ra còn có tòa nhà xml, xây dựng chuỗi và nhiều thứ khác. Hãy tưởng tượng nếu java.util.Map
đã đặt như một người xây dựng. Bạn có thể làm những thứ như thế này:
Map<String, Integer> m = new HashMap<String, Integer>()
.put("a", 1)
.put("b", 2)
.put("c", 3);
Trong khi đi qua Microsoft MVC framework, tôi đã nghĩ về mẫu xây dựng. Tôi đã bắt gặp mô hình trong lớp ControllerBuilder. Lớp này là để trả về lớp nhà máy điều khiển, sau đó được sử dụng để xây dựng bộ điều khiển cụ thể.
Ưu điểm tôi thấy khi sử dụng mẫu xây dựng là, bạn có thể tạo một nhà máy của riêng mình và cắm nó vào khung.
@Tetha, có thể có một nhà hàng (Framework) được điều hành bởi anh chàng người Ý, phục vụ Pizza. Để chuẩn bị pizza, anh chàng người Ý (Object Builder) sử dụng Owen (Factory) với đế bánh pizza (lớp cơ sở).
Bây giờ anh chàng Ấn Độ tiếp quản nhà hàng từ anh chàng người Ý. Nhà hàng Ấn Độ (Framework) máy chủ dosa thay vì pizza. Để chuẩn bị anh chàng người Ấn Độ (người xây dựng đối tượng) sử dụng Frying Pan (Nhà máy) với Maida (lớp cơ sở)
Nếu bạn nhìn vào kịch bản, thực phẩm là khác nhau, cách thức chế biến thức ăn là khác nhau, nhưng trong cùng một nhà hàng (trong cùng một khuôn khổ). Nhà hàng nên được xây dựng theo cách nó có thể hỗ trợ Trung Quốc, Mexico hoặc bất kỳ món ăn nào. Trình xây dựng đối tượng bên trong khung tạo điều kiện cho loại plugin bạn muốn. ví dụ
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;
}
}
Tôi luôn không thích mẫu Builder là một thứ gì đó khó sử dụng, gây khó chịu và rất thường bị lạm dụng bởi các lập trình viên ít kinh nghiệm. Đây là một mẫu chỉ có ý nghĩa nếu bạn cần lắp ráp đối tượng từ một số dữ liệu đòi hỏi bước khởi tạo sau (tức là một khi tất cả dữ liệu được thu thập - làm gì đó với nó). Thay vào đó, trong 99% các nhà xây dựng thời gian chỉ đơn giản được sử dụng để khởi tạo các thành viên của lớp.
Trong những trường hợp như vậy, tốt hơn hết là chỉ cần khai báo withXyz(...)
kiểu setters bên trong lớp và làm cho chúng trả về một tham chiếu đến chính nó.
Xem xét điều này:
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");
Bây giờ chúng ta có một lớp duy nhất gọn gàng quản lý việc khởi tạo của riêng nó và thực hiện khá nhiều công việc tương tự như trình xây dựng, ngoại trừ việc nó thanh lịch hơn nhiều.
Một ưu điểm khác của trình xây dựng là nếu bạn có Factory, vẫn có một số khớp nối trong mã của bạn, bởi vì để Factory hoạt động, nó phải biết tất cả các đối tượng mà nó có thể tạo ra . Nếu bạn thêm một đối tượng khác có thể được tạo, bạn sẽ phải sửa đổi lớp nhà máy để bao gồm anh ta. Điều này cũng xảy ra trong Nhà máy Trừu tượng.
Mặt khác, với trình xây dựng, bạn chỉ cần tạo một trình xây dựng cụ thể mới cho lớp mới này. Lớp giám đốc sẽ giữ nguyên, bởi vì nó nhận được trình xây dựng trong hàm tạo.
Ngoài ra, có nhiều hương vị của người xây dựng. Kamikaze Mercenary `cho một cái khác.
/// <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);
}
}
Dựa trên các câu trả lời trước đó (ý định chơi chữ), một ví dụ tuyệt vời trong thế giới thực là sự hỗ trợ của GroovyBuilders
.
Xem Nhà xây dựng trong Tài liệu Groovy
Tôi đã sử dụng trình xây dựng trong thư viện nhắn tin trồng tại nhà. Lõi thư viện đã nhận dữ liệu từ dây, thu thập nó bằng cá thể Builder, sau đó, khi Builder quyết định nó có mọi thứ cần thiết để tạo một cá thể Message, Builder.GetMessage () đang xây dựng một thể hiện thông báo bằng cách sử dụng dữ liệu được thu thập từ dây điện.
Hãy xem InnerBuilder, một plugin IntelliJ IDEA có thêm hành động 'Trình tạo' vào menu Tạo (Alt + Chèn) để tạo lớp trình tạo bên trong như được mô tả trong Java hiệu quả
Khi tôi muốn sử dụng XMLGregorianCalWiki tiêu chuẩn cho XML của mình để phản đối việc sắp xếp DateTime trong Java, tôi đã nghe rất nhiều ý kiến về việc sử dụng nó có trọng lượng nặng và cồng kềnh như thế nào. Tôi đã cố gắng kiểm tra các trường XML trong các chuỗi xs: datetime để quản lý múi giờ, mili giây, v.v.
Vì vậy, tôi đã thiết kế một tiện ích để xây dựng lịch XMLGregorian từ GregorianCalWiki hoặc java.util.Date.
Vì nơi tôi làm việc, tôi không được phép chia sẻ trực tuyến mà không hợp pháp, nhưng đây là một ví dụ về cách khách hàng sử dụng nó. Nó trừu tượng hóa các chi tiết và lọc một số việc triển khai XMLGregorianCalWiki ít được sử dụng cho xs: datetime.
XMLGregorianCalendarBuilder builder = XMLGregorianCalendarBuilder.newInstance(jdkDate);
XMLGregorianCalendar xmlCalendar = builder.excludeMillis().excludeOffset().build();
Cấp mẫu này là nhiều bộ lọc hơn vì nó đặt các trường trong xmlCalWiki là không xác định để chúng bị loại trừ, nó vẫn "xây dựng" nó. Tôi đã dễ dàng thêm các tùy chọn khác vào trình tạo để tạo xs: date và xs: time struct và cũng để thao tác bù đắp múi giờ khi cần.
Nếu bạn đã từng thấy mã tạo và sử dụng XMLGregorianCalWiki, bạn sẽ thấy cách này giúp thao tác dễ dàng hơn nhiều.
Một ví dụ tuyệt vời trong thế giới thực là sử dụng khi đơn vị kiểm tra các lớp của bạn. Bạn sử dụng các trình xây dựng sut (System Under Test).
Thí dụ:
Lớp học:
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;
}
Kiểm tra:
[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);
}
Người xây dựng 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();
}
}
CustomAuthenticationService
lớp của bạn ?