คุณจะใช้รูปแบบของตัวสร้างเมื่อใด [ปิด]


531

อะไรคือตัวอย่างที่พบเห็นได้ทั่วไปในโลกแห่งความจริงของการใช้รูปแบบของตัวสร้าง คุณซื้ออะไร ทำไมไม่ใช้รูปแบบจากโรงงาน?


stackoverflow.com/questions/35238292/...กล่าวถึง APIs บางอย่างที่ใช้รูปแบบสร้าง
Alireza Fattahi

คำตอบจากAaronและTethaนั้นเป็นข้อมูลจริงๆ นี่คือบทความเต็มรูปแบบที่เกี่ยวข้องกับคำตอบเหล่านั้น
Diablo

คำตอบ:


262

ความแตกต่างที่สำคัญระหว่างผู้สร้างและ IMHO โรงงานคือผู้สร้างมีประโยชน์เมื่อคุณต้องทำสิ่งต่างๆมากมายเพื่อสร้างวัตถุ ตัวอย่างเช่นลองนึกภาพ DOM คุณต้องสร้างโหนดและคุณสมบัติมากมายเพื่อให้ได้ออบเจ็กต์สุดท้ายของคุณ โรงงานถูกใช้เมื่อโรงงานสามารถสร้างวัตถุทั้งหมดได้อย่างง่ายดายภายในการเรียกใช้เมธอดเดียว

ตัวอย่างหนึ่งของการใช้ตัวสร้างคือการสร้างเอกสาร XML ฉันใช้โมเดลนี้เมื่อสร้างชิ้นส่วน HTML ตัวอย่างเช่นฉันอาจมีตัวสร้างสำหรับการสร้างตารางประเภทเฉพาะและอาจมีวิธีการต่อไปนี้(พารามิเตอร์จะไม่แสดง) :

BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()

ผู้สร้างนี้จะคาย HTML ให้ฉัน อ่านง่ายกว่าการเดินตามวิธีการที่มีขนาดใหญ่

ตรวจสอบรูปแบบสร้างวิกิพีเดีย


1021

ด้านล่างมีเหตุผลบางอย่างเถียงสำหรับการใช้รูปแบบและตัวอย่างโค้ดในชวา แต่มันคือการดำเนินการรูปแบบสร้างครอบคลุมโดยแก๊งสี่ในรูปแบบการออกแบบ เหตุผลที่คุณจะใช้ใน 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 ที่มีประสิทธิภาพอื่น ๆฉันขอแนะนำ


24
ต่างจากตัวสร้าง GOF ดั้งเดิมใช่ไหม เนื่องจากไม่มี Director Class ดูเหมือนจะเป็นอีกรูปแบบหนึ่งสำหรับฉัน แต่ฉันเห็นด้วยว่ามันมีประโยชน์มาก
Lino Rosa

194
สำหรับตัวอย่างนี้โดยเฉพาะอย่างยิ่งมันจะไม่ดีกว่าที่จะเอาพารามิเตอร์บูลีนและสามารถที่จะพูดnew Pizza.Builder(12).cheese().pepperoni().bacon().build();
Fabian Steeg

46
ลักษณะนี้มากขึ้นเช่นการเชื่อมต่อคล่องแคล่วกว่ารูปแบบการสร้าง
Robert Harvey

21
@ Fabian Steeg ฉันคิดว่าผู้คนกำลังทำปฏิกิริยากับ setter บูลีนที่ดูดีกว่าโปรดจำไว้ว่า setters ประเภทนี้ไม่อนุญาตให้มีการเปลี่ยนแปลงในรันไทม์: Pizza.Builder(12).cheese().pepperoni().bacon().build();คุณจะต้องคอมไพล์โค้ดของคุณใหม่หรือมีตรรกะที่ไม่จำเป็น พิซซ่า อย่างน้อยที่สุดคุณก็ควรจัดเตรียมเวอร์ชั่นที่เป็นพารามิเตอร์เช่น @Kamikaze Mercenary ที่แนะนำไว้ในตอนแรก Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();. จากนั้นอีกครั้งเราไม่เคยทดสอบหน่วยใช่หรือไม่
egallardo

23
@JasonC ใช่แล้วพิซซ่าอะไรที่ใช้ไม่ได้แล้วล่ะ?
Maarten Bodewes

325

พิจารณาร้านอาหาร การสร้าง "มื้ออาหารของวันนี้" เป็นรูปแบบของโรงงานเพราะคุณบอกห้องครัวว่า "รับอาหารของฉันวันนี้" และห้องครัว (โรงงาน) จะตัดสินใจว่าวัตถุใดที่จะสร้างตามเกณฑ์ที่ซ่อนอยู่

ตัวสร้างจะปรากฏขึ้นหากคุณสั่งพิซซ่าแบบกำหนดเอง ในกรณีนี้บริกรบอกพ่อครัว (ผู้สร้าง) "ฉันต้องการพิซซ่า; เพิ่มชีสหัวหอมและเบคอน!" ดังนั้นผู้สร้างจะเปิดเผยคุณสมบัติที่วัตถุที่สร้างขึ้นควรมี แต่ซ่อนวิธีการตั้งค่า


Nitin ขยายความคล้ายคลึงของครัวในคำตอบของคำถามนี้
Pops

19

.NET StringBuilder class เป็นตัวอย่างที่ดีของรูปแบบการสร้าง ส่วนใหญ่จะใช้ในการสร้างสตริงในชุดของขั้นตอน ผลลัพธ์สุดท้ายที่คุณได้รับจากการทำ ToString () เป็นสตริงเสมอ แต่การสร้างสตริงนั้นจะแตกต่างกันไปตามฟังก์ชั่นที่ใช้ในคลาส StringBuilder สรุปแล้วแนวคิดพื้นฐานคือการสร้างวัตถุที่ซับซ้อนและซ่อนรายละเอียดการใช้งานว่ามันถูกสร้างขึ้นอย่างไร


9
ฉันไม่คิดว่านั่นเป็นรูปแบบการสร้าง StringBuilder เป็นเพียงการนำไปใช้งานของคลาสอาเรย์อักขระ (เช่นสตริง) แต่ทำให้การจัดการประสิทธิภาพและหน่วยความจำเข้ามาในบัญชีเนื่องจากสตริงนั้นไม่เปลี่ยนรูป
ชาร์ลส์เกรแฮม

23
มันเป็นรูปแบบการสร้างอย่างแน่นอนเช่นเดียวกับคลาส StringBuilder ใน Java แจ้งให้ทราบว่าผนวก () วิธีการทั้งสองของชั้นเรียนเหล่านี้ผลตอบแทน StringBuilder ตัวเองเพื่อให้ห่วงโซ่กระป๋องหนึ่งก่อนที่จะเรียกb.append(...).append(...) toString()การอ้างอิง: infoq.com/articles/internal-dsls-java
pohl

3
@pohl Ya ฉันไม่คิดว่ามันเป็นรูปแบบการสร้างจริงๆฉันพูดได้ว่าเป็นเพียงส่วนต่อประสานที่คล่องแคล่ว
Didier A.

"สังเกตวิธีการผนวก () วิธีการของทั้งสองชั้นเรียนเหล่านี้ส่งกลับ StringBuilder ตัวเอง" นั่นไม่ใช่รูปแบบการสร้างนั่นเป็นเพียงส่วนต่อประสานที่คล่องแคล่ว เพียงว่ามันมักจะสร้างจะใช้อินเตอร์เฟซได้อย่างคล่องแคล่ว ตัวสร้างไม่จำเป็นต้องมีส่วนต่อประสานที่คล่องแคล่ว
bytedev

แต่โปรดทราบว่า StringBuilder โดยธรรมชาติจะไม่ถูกซิงโครไนซ์ซึ่งแตกต่างจาก StringBuffer ซึ่งถูกซิงโครไนซ์
Alan Deep

11

สำหรับปัญหาแบบมัลติเธรดเราต้องการวัตถุที่ซับซ้อนเพื่อสร้างขึ้นสำหรับแต่ละเธรด วัตถุแสดงข้อมูลที่กำลังประมวลผลและสามารถเปลี่ยนแปลงได้ขึ้นอยู่กับอินพุตของผู้ใช้

เราสามารถใช้โรงงานแทนได้หรือไม่ ใช่

ทำไมเราไม่ เครื่องมือสร้างทำให้ฉันเข้าใจได้ง่ายขึ้น

โรงงานใช้สำหรับสร้างวัตถุประเภทต่าง ๆ ที่เป็นประเภทพื้นฐานเดียวกัน (ใช้อินเทอร์เฟซหรือคลาสพื้นฐานเดียวกัน)

ผู้สร้างสร้างวัตถุชนิดเดียวกันซ้ำไปซ้ำมา แต่การก่อสร้างเป็นแบบไดนามิกดังนั้นจึงสามารถเปลี่ยนได้ในขณะทำงาน


9

คุณใช้มันเมื่อคุณมีตัวเลือกมากมายให้จัดการ คิดเกี่ยวกับสิ่งต่าง ๆ เช่น 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);

3
ฉันลืมที่จะอ่านแผนที่ "ถ้า" ใช้รูปแบบการสร้างและประหลาดใจที่เห็นการสร้างที่นั่น .. :)
sethu

3
:) ขอโทษด้วยกับเรื่องนั้น. เป็นเรื่องปกติในหลายภาษาที่จะกลับมาเป็นตัวของตัวเองแทนที่จะเป็นโมฆะ คุณสามารถทำได้ด้วยภาษาจาวา แต่มันไม่ธรรมดา
ดัสติน

7
ตัวอย่างแผนที่เป็นเพียงตัวอย่างของวิธีการผูกมัด
nogridbag

@nogridbag จริงๆแล้วมันจะเข้าใกล้เมธอด cascading แม้ว่ามันจะใช้การผูกมัดในแบบที่จำลองการซ้อนดังนั้นจึงเห็นได้ชัดว่าเป็นการผูกมัด แต่ความหมายมันทำหน้าที่เหมือนการต่อเรียง
Didier A.

9

ในขณะที่ผ่านกรอบงาน 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;
   }
}

7

ฉันไม่ชอบรูปแบบของตัวสร้างเสมอว่าเป็นสิ่งที่ไม่สะดวกไม่น่าไว้วางใจและถูกใช้บ่อยโดยโปรแกรมเมอร์ที่มีประสบการณ์น้อยกว่า เป็นรูปแบบที่เหมาะสมถ้าคุณต้องการรวบรวมวัตถุจากข้อมูลบางอย่างที่ต้องใช้ขั้นตอนการโพสต์เริ่มต้น (เช่นเมื่อรวบรวมข้อมูลทั้งหมดแล้วให้ทำอะไรกับมัน) แต่ใน 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");

ตอนนี้เรามีคลาสเดี่ยวที่เรียบร้อยซึ่งจัดการการกำหนดค่าเริ่มต้นของตัวเองและทำงานค่อนข้างเหมือนกับผู้สร้างยกเว้นว่ามันจะสวยกว่า


ฉันเพิ่งตัดสินใจว่าฉันต้องการสร้างเอกสาร XML ที่ซับซ้อนของฉันเป็น JSON ประการแรกฉันจะรู้ได้อย่างไรว่าคลาส 'Complex' สามารถส่งมอบผลิตภัณฑ์ XMLable ได้ตั้งแต่แรกและฉันจะเปลี่ยนเป็นวัตถุ JSONable ได้อย่างไร คำตอบด่วน: ฉันทำไม่ได้เพราะฉันจำเป็นต้องใช้ผู้สร้าง และเรามาเต็มวง ...
David Barker

1
รวม bs, Builder ถูกออกแบบมาเพื่อสร้างวัตถุที่ไม่เปลี่ยนรูปและด้วยความสามารถในการเปลี่ยนวิธีการสร้างในอนาคตโดยไม่ต้องสัมผัสระดับผลิตภัณฑ์
มิกกี้ Tin

6
อืมม? คุณอ่านคำตอบที่ฉันบอกว่า Builder ออกแบบมาเพื่ออะไร? มันเป็นอีกมุมมองหนึ่งของคำถามที่ด้านบน "เมื่อใดที่คุณจะใช้รูปแบบของตัวสร้าง?" ซึ่งขึ้นอยู่กับประสบการณ์ของการใช้รูปแบบที่ไม่เหมาะสมซึ่งสิ่งที่ง่ายกว่าทำงานได้ดีกว่ามาก รูปแบบทั้งหมดมีประโยชน์ถ้าคุณรู้ว่าจะใช้เมื่อไหร่และอย่างไร - นั่นคือจุดรวมของการจัดทำเอกสารรูปแบบในตอนแรก! เมื่อรูปแบบถูกใช้มากเกินไปหรือแย่ลง - ถูกใช้ในทางที่ผิด - จากนั้นจะกลายเป็นรูปแบบการต่อต้านภายในบริบทของรหัสของคุณ อนิจจา ...
Pavel Lechev

6

ข้อดีอีกอย่างของผู้สร้างคือถ้าคุณมี Factory ก็ยังมีการเชื่อมต่อบางอย่างในรหัสของคุณเพราะเพื่อให้โรงงานทำงานได้มันต้องรู้วัตถุทั้งหมดที่มันสามารถสร้างได้ หากคุณเพิ่มวัตถุอื่นที่สามารถสร้างขึ้นได้คุณจะต้องแก้ไขคลาสของโรงงานเพื่อรวมไว้ สิ่งนี้เกิดขึ้นใน Abstract Factory เช่นกัน

ในขณะที่ผู้สร้างคุณเพียงแค่สร้างตัวสร้างคอนกรีตใหม่สำหรับคลาสใหม่นี้ คลาสผู้กำกับจะยังคงเหมือนเดิมเพราะจะได้รับการสร้างในตัวสร้าง

นอกจากนี้ยังมีผู้สร้างรสชาติมากมาย Kamikaze Mercenary`s ให้อีกอันหนึ่ง


6
/// <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);
    }
}

1
คุณสามารถปรับปรุงคำตอบของคุณได้สองวิธี: 1) ทำให้เป็น SSCCE 2) อธิบายว่าสิ่งนี้ตอบคำถามได้อย่างไร
james.garriss

6

อาคารในคำตอบก่อนหน้า (เล่นสำนวนเจตนา) ตัวอย่างจริงของโลกที่ยอดเยี่ยมคือGroovy 's Buildersสร้างในการสนับสนุน

ดูBuildersในเอกสาร Groovy


3

ฉันใช้ช่างก่อสร้างในห้องสมุดรับส่งข้อความที่บ้าน แกนไลบรารีได้รับข้อมูลจากการรวบรวมโดยใช้อินสแตนซ์ของตัวสร้างจากนั้นเมื่อตัวสร้างตัดสินใจว่ามันมีทุกอย่างที่จำเป็นในการสร้างอินสแตนซ์ข้อความ Builder.GetMessage () กำลังสร้างอินสแตนซ์ข้อความโดยใช้ข้อมูลที่รวบรวมจาก ลวด



2

เมื่อฉันต้องการใช้ 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 คุณจะเห็นว่าสิ่งนี้ทำให้การจัดการทำได้ง่ายขึ้นมาก


0

ตัวอย่างโลกแห่งความจริงที่ยิ่งใหญ่คือการใช้เมื่อหน่วยทดสอบชั้นเรียนของคุณ คุณใช้ผู้สร้าง 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ชั้นเรียนของคุณ
Laur Ivan

นั่นเป็นคำถามที่ดี @LaurIvan! บางทีตัวอย่างของฉันอาจดูแย่ แต่ลองจินตนาการว่าคุณไม่สามารถเปลี่ยนคลาส CustomAuthenticationService ผู้สร้างจะเป็นวิธีที่น่าสนใจในการปรับปรุงการทดสอบหน่วยการอ่านของคุณ และฉันคิดว่าการสร้าง getters และ setters จะเปิดเผยข้อมูลของคุณซึ่งจะใช้สำหรับการทดสอบของคุณ หากคุณต้องการเพิ่มเติมเกี่ยวกับ Sut Builder คุณสามารถอ่านเกี่ยวกับTest Data Builder ได้ซึ่งจะเหมือนกัน แต่สำหรับ Suts
Rafael Miceli
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.