อะไรคือตัวอย่างที่พบเห็นได้ทั่วไปในโลกแห่งความจริงของการใช้รูปแบบของตัวสร้าง คุณซื้ออะไร ทำไมไม่ใช้รูปแบบจากโรงงาน?
อะไรคือตัวอย่างที่พบเห็นได้ทั่วไปในโลกแห่งความจริงของการใช้รูปแบบของตัวสร้าง คุณซื้ออะไร ทำไมไม่ใช้รูปแบบจากโรงงาน?
คำตอบ:
ความแตกต่างที่สำคัญระหว่างผู้สร้างและ IMHO โรงงานคือผู้สร้างมีประโยชน์เมื่อคุณต้องทำสิ่งต่างๆมากมายเพื่อสร้างวัตถุ ตัวอย่างเช่นลองนึกภาพ DOM คุณต้องสร้างโหนดและคุณสมบัติมากมายเพื่อให้ได้ออบเจ็กต์สุดท้ายของคุณ โรงงานถูกใช้เมื่อโรงงานสามารถสร้างวัตถุทั้งหมดได้อย่างง่ายดายภายในการเรียกใช้เมธอดเดียว
ตัวอย่างหนึ่งของการใช้ตัวสร้างคือการสร้างเอกสาร XML ฉันใช้โมเดลนี้เมื่อสร้างชิ้นส่วน HTML ตัวอย่างเช่นฉันอาจมีตัวสร้างสำหรับการสร้างตารางประเภทเฉพาะและอาจมีวิธีการต่อไปนี้(พารามิเตอร์จะไม่แสดง) :
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
ผู้สร้างนี้จะคาย HTML ให้ฉัน อ่านง่ายกว่าการเดินตามวิธีการที่มีขนาดใหญ่
ด้านล่างมีเหตุผลบางอย่างเถียงสำหรับการใช้รูปแบบและตัวอย่างโค้ดในชวา แต่มันคือการดำเนินการรูปแบบสร้างครอบคลุมโดยแก๊งสี่ในรูปแบบการออกแบบ เหตุผลที่คุณจะใช้ใน Java นั้นยังสามารถใช้ได้กับภาษาการเขียนโปรแกรมอื่นเช่นกัน
ดังที่ Joshua Bloch ระบุในJava ที่มีประสิทธิภาพรุ่นที่ 2 :
รูปแบบตัวสร้างเป็นตัวเลือกที่ดีเมื่อออกแบบคลาสที่คอนสตรัคเตอร์หรือโรงงานคงที่จะมีมากกว่าหนึ่งพารามิเตอร์
ในบางครั้งเราทุกคนจะพบกับคลาสที่มีรายชื่อคอนสตรัคเตอร์ซึ่งการเพิ่มแต่ละครั้งจะเพิ่มพารามิเตอร์ตัวเลือกใหม่:
Pizza(int size) { ... }
Pizza(int size, boolean cheese) { ... }
Pizza(int size, boolean cheese, boolean pepperoni) { ... }
Pizza(int size, boolean cheese, boolean pepperoni, boolean bacon) { ... }
สิ่งนี้เรียกว่า Telescoping Constructor Pattern ปัญหาเกี่ยวกับรูปแบบนี้คือว่าเมื่อการก่อสร้างเป็น 4 หรือ 5 พารามิเตอร์ยาวมันจะกลายเป็นเรื่องยากที่จะจำที่จำเป็นในการสั่งซื้อของพารามิเตอร์เช่นเดียวกับสิ่งคอนสตรัคโดยเฉพาะอย่างยิ่งที่คุณอาจต้องการในสถานการณ์ที่กำหนด
อีกทางเลือกหนึ่งที่คุณต้องใช้กับ Telescoping Constructor Pattern คือJavaBean Patternที่คุณเรียกใช้ Constructor ด้วยพารามิเตอร์ที่จำเป็นและจากนั้นเรียกใช้ setters เสริมอื่น ๆ หลังจาก:
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;
}
}
โปรดทราบว่าพิซซ่าจะไม่เปลี่ยนรูปและค่าพารามิเตอร์ที่มีทั้งหมดในสถานที่เดียว เนื่องจากเมธอด setter ของตัวสร้างส่งคืนอ็อบเจ็กต์ตัวสร้างจึงสามารถถูกโยงโซ่ได้
Pizza pizza = new Pizza.Builder(12)
.cheese(true)
.pepperoni(true)
.bacon(true)
.build();
ผลลัพธ์นี้ในรหัสที่ง่ายต่อการเขียนและง่ายต่อการอ่านและทำความเข้าใจ ในตัวอย่างนี้วิธีการบิลด์สามารถปรับเปลี่ยนเพื่อตรวจสอบพารามิเตอร์หลังจากที่คัดลอกจากตัวสร้างไปยังวัตถุ Pizza และโยน IllegalStateException หากมีการกำหนดค่าพารามิเตอร์ที่ไม่ถูกต้อง รูปแบบนี้มีความยืดหยุ่นและง่ายต่อการเพิ่มพารามิเตอร์เพิ่มเติมในอนาคต มันมีประโยชน์จริงๆถ้าคุณจะมีพารามิเตอร์มากกว่า 4 หรือ 5 พารามิเตอร์สำหรับคอนสตรัคเตอร์ ที่กล่าวมาอาจเป็นสิ่งที่มีค่าในตอนแรกหากคุณสงสัยว่าคุณอาจเพิ่มพารามิเตอร์เพิ่มเติมในอนาคต
ฉันยืมมามากในหัวข้อนี้จากหนังสือEffective Java, 2nd Editionโดย Joshua Bloch เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับรูปแบบนี้และแนวทางปฏิบัติ 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 class เป็นตัวอย่างที่ดีของรูปแบบการสร้าง ส่วนใหญ่จะใช้ในการสร้างสตริงในชุดของขั้นตอน ผลลัพธ์สุดท้ายที่คุณได้รับจากการทำ 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 สามารถมีร้านอาหาร (Framework) ดำเนินการโดยคนอิตาลีที่ให้บริการพิซซ่า ในการจัดเตรียมพิซซ่าอิตาเลี่ยนคนสร้างวัตถุ (Object Builder) ใช้โอเวน (โรงงาน) กับฐานพิซซ่า (คลาสฐาน)
ตอนนี้คนอินเดียรับช่วงต่อจากร้านอาหารของคนอิตาลี เซิร์ฟเวอร์ร้านอาหารอินเดีย (Framework) dosa แทนพิซซ่า เพื่อเตรียมความพร้อม dosa คนอินเดีย (ผู้สร้างวัตถุ) ใช้ Frying Pan (Factory) กับ 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;
}
}
ฉันไม่ชอบรูปแบบของตัวสร้างเสมอว่าเป็นสิ่งที่ไม่สะดวกไม่น่าไว้วางใจและถูกใช้บ่อยโดยโปรแกรมเมอร์ที่มีประสบการณ์น้อยกว่า เป็นรูปแบบที่เหมาะสมถ้าคุณต้องการรวบรวมวัตถุจากข้อมูลบางอย่างที่ต้องใช้ขั้นตอนการโพสต์เริ่มต้น (เช่นเมื่อรวบรวมข้อมูลทั้งหมดแล้วให้ทำอะไรกับมัน) แต่ใน 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 ก็ยังมีการเชื่อมต่อบางอย่างในรหัสของคุณเพราะเพื่อให้โรงงานทำงานได้มันต้องรู้วัตถุทั้งหมดที่มันสามารถสร้างได้ หากคุณเพิ่มวัตถุอื่นที่สามารถสร้างขึ้นได้คุณจะต้องแก้ไขคลาสของโรงงานเพื่อรวมไว้ สิ่งนี้เกิดขึ้นใน Abstract Factory เช่นกัน
ในขณะที่ผู้สร้างคุณเพียงแค่สร้างตัวสร้างคอนกรีตใหม่สำหรับคลาสใหม่นี้ คลาสผู้กำกับจะยังคงเหมือนเดิมเพราะจะได้รับการสร้างในตัวสร้าง
นอกจากนี้ยังมีผู้สร้างรสชาติมากมาย Kamikaze 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);
}
}
อาคารในคำตอบก่อนหน้า (เล่นสำนวนเจตนา) ตัวอย่างจริงของโลกที่ยอดเยี่ยมคือGroovy 's Builders
สร้างในการสนับสนุน
ฉันใช้ช่างก่อสร้างในห้องสมุดรับส่งข้อความที่บ้าน แกนไลบรารีได้รับข้อมูลจากการรวบรวมโดยใช้อินสแตนซ์ของตัวสร้างจากนั้นเมื่อตัวสร้างตัดสินใจว่ามันมีทุกอย่างที่จำเป็นในการสร้างอินสแตนซ์ข้อความ Builder.GetMessage () กำลังสร้างอินสแตนซ์ข้อความโดยใช้ข้อมูลที่รวบรวมจาก ลวด
ลองใช้ InnerBuilder ปลั๊กอิน IntelliJ IDEA ที่เพิ่มการกระทำ 'Builder' ลงในเมนูสร้าง (แทรก + Alt) ซึ่งสร้างคลาสตัวสร้างภายในตามที่อธิบายไว้ใน Java ที่มีประสิทธิภาพ
เมื่อฉันต้องการใช้ XMLGregorianCalendar มาตรฐานสำหรับ XML ของฉันในการคัดค้าน DateTime ใน Java ฉันได้ยินความคิดเห็นจำนวนมากเกี่ยวกับการใช้งานที่หนักและยุ่งยาก ฉันพยายามที่จะ comtrol เขตข้อมูล XML ใน xs: datetime structs เพื่อจัดการเขตเวลามิลลิวินาทีเป็นต้น
ดังนั้นฉันจึงออกแบบยูทิลิตี้เพื่อสร้างปฏิทิน XMLGregorian จาก GregorianCalendar หรือ java.util.Date
เนื่องจากที่ทำงานของฉันฉันไม่ได้รับอนุญาตให้แบ่งปันทางออนไลน์โดยไม่มีกฎหมาย แต่นี่เป็นตัวอย่างของวิธีที่ลูกค้าใช้งาน มันสรุปรายละเอียดและกรองการใช้งานบางอย่างของ XMLGregorianCalendar ที่ใช้น้อยกว่าสำหรับ xs: datetime
XMLGregorianCalendarBuilder builder = XMLGregorianCalendarBuilder.newInstance(jdkDate);
XMLGregorianCalendar xmlCalendar = builder.excludeMillis().excludeOffset().build();
ได้รับรูปแบบนี้เป็นตัวกรองมากกว่าที่มันตั้งค่าเขตข้อมูลใน xmlCalendar เป็นไม่ได้กำหนดดังนั้นพวกเขาจะได้รับการยกเว้นมันยังคง "สร้าง" มัน ฉันได้เพิ่มตัวเลือกอื่น ๆ ลงในตัวสร้างอย่างง่ายดายเพื่อสร้าง xs: date และ xs: time struct และเพื่อจัดการ offsets เขตเวลาเมื่อจำเป็น
หากคุณเคยเห็นรหัสที่สร้างและใช้ 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:
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
ชั้นเรียนของคุณ