Kapan Anda akan menggunakan Pola Builder? [Tutup]


531

Apa saja contoh umum , dunia nyata dalam menggunakan Pola Builder? Apa yang membelikanmu? Mengapa tidak menggunakan Pola Pabrik?


The stackoverflow.com/questions/35238292/… menyebutkan beberapa API yang menggunakan pola builder
Alireza Fattahi

Jawaban dari Aaron dan Tetha sangat informatif. Inilah artikel lengkap yang terkait dengan jawaban-jawaban itu.
Diablo

Jawaban:


262

Perbedaan utama antara pembangun dan pabrik IMHO, adalah pembangun berguna ketika Anda perlu melakukan banyak hal untuk membangun objek. Misalnya bayangkan DOM. Anda harus membuat banyak node dan atribut untuk mendapatkan objek akhir Anda. Pabrik digunakan ketika pabrik dapat dengan mudah membuat seluruh objek dalam satu panggilan metode.

Salah satu contoh menggunakan pembangun adalah membangun dokumen XML, saya telah menggunakan model ini ketika membangun fragmen HTML misalnya saya mungkin memiliki Builder untuk membangun jenis tabel tertentu dan mungkin memiliki metode berikut (parameter tidak ditampilkan) :

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

Pembangun ini kemudian akan memuntahkan HTML untuk saya. Ini jauh lebih mudah dibaca daripada berjalan melalui metode prosedural yang besar.

Lihat Pola Builder di Wikipedia .


1021

Berikut adalah beberapa alasan yang memperdebatkan penggunaan pola dan kode contoh di Jawa, tetapi ini merupakan implementasi dari Pola Builder yang dicakup oleh Geng Empat dalam Pola Desain . Alasan Anda menggunakannya di Jawa juga berlaku untuk bahasa pemrograman lain.

Seperti yang dikatakan Joshua Bloch dalam Java Efektif, Edisi ke-2 :

Pola pembangun adalah pilihan yang baik ketika merancang kelas yang konstruktor atau pabrik statisnya akan memiliki lebih dari beberapa parameter.

Kita semua pada beberapa titik menghadapi kelas dengan daftar konstruktor di mana setiap penambahan menambahkan parameter opsi baru:

Pizza(int size) { ... }        
Pizza(int size, boolean cheese) { ... }    
Pizza(int size, boolean cheese, boolean pepperoni) { ... }    
Pizza(int size, boolean cheese, boolean pepperoni, boolean bacon) { ... }

Ini disebut Pola Konstruktor Telescoping. Masalah dengan pola ini adalah bahwa setelah konstruktor 4 atau 5 parameter panjang menjadi sulit untuk mengingat yang diperlukan urutan parameter serta apa konstruktor tertentu Anda mungkin ingin dalam situasi tertentu.

Salah satu alternatif yang Anda miliki untuk Pola Konstruktor Telescoping adalah Pola JavaBean di mana Anda memanggil konstruktor dengan parameter wajib dan kemudian memanggil setter opsional setelah:

Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);

Masalahnya di sini adalah bahwa karena objek dibuat melalui beberapa panggilan itu mungkin dalam keadaan tidak konsisten di tengah-tengah pembangunannya. Ini juga membutuhkan banyak upaya ekstra untuk memastikan keamanan benang.

Alternatif yang lebih baik adalah dengan menggunakan Pola 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;
  }
}

Perhatikan bahwa Pizza tidak dapat diubah dan bahwa nilai parameter semuanya dalam satu lokasi . Karena metode setter Builder mengembalikan objek Builder, mereka dapat dirantai .

Pizza pizza = new Pizza.Builder(12)
                       .cheese(true)
                       .pepperoni(true)
                       .bacon(true)
                       .build();

Ini menghasilkan kode yang mudah ditulis dan sangat mudah dibaca dan dimengerti. Dalam contoh ini, metode build dapat dimodifikasi untuk memeriksa parameter setelah mereka disalin dari builder ke objek Pizza dan melempar IllegalStateException jika nilai parameter tidak valid telah diberikan. Pola ini fleksibel dan mudah untuk menambahkan lebih banyak parameter ke depannya. Ini benar-benar hanya berguna jika Anda akan memiliki lebih dari 4 atau 5 parameter untuk konstruktor. Yang mengatakan, mungkin bermanfaat pada awalnya jika Anda curiga Anda mungkin menambahkan lebih banyak parameter di masa depan.

Saya telah meminjam banyak pada topik ini dari buku Effective Java, 2nd Edition oleh Joshua Bloch. Untuk mempelajari lebih lanjut tentang pola ini dan praktik Java efektif lainnya, saya sangat merekomendasikannya.


24
Berbeda dengan pembuat GOF asli, kan? Karena tidak ada Kelas Direktur. Sepertinya hampir seperti pola lain bagi saya, tetapi saya setuju itu sangat berguna.
Lino Rosa

194
Untuk contoh khusus ini, bukankah akan lebih baik untuk menghapus parameter boolean dan dapat mengatakannew Pizza.Builder(12).cheese().pepperoni().bacon().build();
Fabian Steeg

46
Ini lebih mirip Antarmuka Lancar daripada pola pembangun .
Robert Harvey

21
@Fabian Steeg, saya pikir orang bereaksi berlebihan terhadap setter boolean yang tampak lebih bagus, perlu diingat bahwa setter seperti ini tidak memungkinkan perubahan runtime:, Pizza.Builder(12).cheese().pepperoni().bacon().build();Anda perlu mengkompilasi ulang kode Anda atau memiliki logika yang tidak perlu jika Anda hanya memerlukan pepperoni pizza. Paling tidak Anda juga harus memberikan versi parametrized seperti yang disarankan @Kamikaze Mercenary. Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();. Kemudian lagi, kita tidak pernah menguji unit, bukan?
egallardo

23
@JasonC Benar, dan apa gunanya pizza abadi?
Maarten Bodewes

325

Pertimbangkan sebuah restoran. Penciptaan "makanan hari ini" adalah pola pabrik, karena Anda memberi tahu dapur "ambilkan saya makanan hari ini" dan dapur (pabrik) memutuskan objek apa yang akan dihasilkan, berdasarkan kriteria tersembunyi.

Pembuat akan muncul jika Anda memesan pizza khusus. Dalam hal ini, pelayan memberi tahu koki (pembangun), "Saya butuh pizza; tambahkan keju, bawang, dan daging asap!" Dengan demikian, pembangun memperlihatkan atribut yang seharusnya dimiliki objek, tetapi menyembunyikan cara mengaturnya.


Nitin memperluas analogi dapur dalam jawaban lain untuk pertanyaan ini .
Pops

19

.NET StringBuilder class adalah contoh yang bagus dari pola builder. Sebagian besar digunakan untuk membuat string dalam serangkaian langkah. Hasil akhir yang Anda dapatkan saat melakukan ToString () selalu berupa string tetapi pembuatan string tersebut bervariasi sesuai dengan fungsi apa yang digunakan di kelas StringBuilder. Singkatnya, ide dasarnya adalah untuk membangun objek yang kompleks dan menyembunyikan detail implementasi bagaimana itu sedang dibangun.


9
Saya tidak berpikir bahwa itu adalah pola pembangun. StringBuilder hanyalah implementasi lain dari kelas array karakter (yaitu string), tetapi membuat kinerja dan manajemen memori diperhitungkan, karena string tidak dapat diubah.
Charles Graham

23
Ini benar-benar pola pembangun, seperti kelas StringBuilder di Jawa. Perhatikan bagaimana metode append () dari kedua kelas ini mengembalikan StringBuilder itu sendiri, sehingga orang dapat berantai b.append(...).append(...)sebelum akhirnya memanggil toString(). Kutipan: infoq.com/articles/internal-dsls-java
pohl

3
@pohl Ya Saya tidak berpikir itu benar-benar pola pembangun, saya akan mengatakan itu lebih hanya antarmuka yang lancar.
Didier A.

"Perhatikan bagaimana metode append () dari kedua kelas ini mengembalikan StringBuilder itu sendiri" itu bukan pola Builder, itu hanya antarmuka yang lancar. Hanya saja, sering kali Builder juga akan menggunakan antarmuka yang lancar. Builder tidak harus memiliki antarmuka yang lancar.
bytedev

Tetapi perhatikan bahwa StringBuilder secara alami tidak disinkronkan, tidak seperti StringBuffer yang disinkronkan.
Alan Deep

11

Untuk masalah multi-utas, kami membutuhkan objek kompleks yang akan dibangun untuk setiap utas. Objek mewakili data yang sedang diproses, dan bisa berubah tergantung pada input pengguna.

Bisakah kita menggunakan pabrik saja? Iya

Kenapa kita tidak melakukannya? Builder lebih masuk akal kurasa.

Pabrik digunakan untuk membuat berbagai jenis objek yang memiliki tipe dasar yang sama (mengimplementasikan antarmuka atau kelas dasar yang sama).

Pembangun membangun jenis objek yang sama berulang-ulang, tetapi konstruksinya dinamis sehingga dapat diubah saat runtime.


9

Anda menggunakannya ketika Anda memiliki banyak opsi untuk dihadapi. Pikirkan tentang hal-hal seperti jmock:

m.expects(once())
    .method("testMethod")
    .with(eq(1), eq(2))
    .returns("someResponse");

Rasanya jauh lebih alami dan ... mungkin.

Ada juga xml building, string building, dan banyak hal lainnya. Bayangkan jika java.util.Mapsudah dimasukkan sebagai pembangun. Anda dapat melakukan hal-hal seperti ini:

Map<String, Integer> m = new HashMap<String, Integer>()
    .put("a", 1)
    .put("b", 2)
    .put("c", 3);

3
Saya lupa membaca peta "jika" menerapkan pola pembangun dan terkejut melihat konstruk di sana .. :)
sethu

3
:) Maaf soal itu. Adalah umum dalam banyak bahasa untuk mengembalikan diri bukannya batal. Anda dapat melakukannya di java, tetapi itu tidak terlalu umum.
Dustin

7
Contoh peta hanyalah contoh metode chaining.
nogridbag

@nogridbag Ini sebenarnya akan lebih dekat dengan metode cascading. Meskipun menggunakan rantai dengan cara yang mensimulasikan cascading, jadi itu jelas chaining, tetapi secara semantik berperilaku cascading.
Didier A.

9

Saat melalui kerangka kerja Microsoft MVC, saya mendapat pemikiran tentang pola pembangun. Saya menemukan pola di kelas ControllerBuilder. Kelas ini untuk mengembalikan kelas pabrik pengontrol, yang kemudian digunakan untuk membangun pengontrol beton.

Keuntungan yang saya lihat dalam menggunakan pola builder adalah, Anda dapat membuat pabrik sendiri dan memasukkannya ke dalam kerangka kerja.

@Tetha, mungkin ada restoran (Kerangka kerja) yang dijalankan oleh pria Italia, yang menyajikan Pizza. Untuk menyiapkan pizza pria Italia (Object Builder) menggunakan Owen (Factory) dengan basis pizza (kelas dasar).

Sekarang pria India mengambil alih restoran dari pria Italia. Restoran India (Kerangka kerja) server dosa bukannya pizza. Untuk mempersiapkan dosa orang India (pembangun objek) menggunakan Frying Pan (Factory) dengan Maida (kelas dasar)

Jika Anda melihat skenario, makanan berbeda, cara menyiapkan makanan berbeda, tetapi di restoran yang sama (di bawah kerangka kerja yang sama). Restoran harus dibangun sedemikian rupa sehingga dapat mendukung masakan Cina, Meksiko atau lainnya. Pembangun objek di dalam kerangka kerja memfasilitasi plugin jenis masakan yang Anda inginkan. sebagai contoh

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

Saya selalu tidak menyukai pola Builder sebagai sesuatu yang berat, menonjol dan sangat sering disalahgunakan oleh programmer yang kurang berpengalaman. Ini adalah pola yang hanya masuk akal jika Anda perlu mengumpulkan objek dari beberapa data yang memerlukan langkah post-inisialisasi (yaitu setelah semua data dikumpulkan - lakukan sesuatu dengannya). Sebaliknya, dalam 99% pembangun waktu hanya digunakan untuk menginisialisasi anggota kelas.

Dalam kasus seperti itu, jauh lebih baik untuk mendeklarasikan withXyz(...)tipe setter di dalam kelas dan membuatnya mengembalikan referensi ke dirinya sendiri.

Pertimbangkan ini:

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");

Sekarang kami memiliki kelas tunggal yang rapi yang mengelola inisialisasi sendiri dan melakukan pekerjaan yang hampir sama dengan pembangun, kecuali bahwa kelasnya jauh lebih elegan.


Saya baru saja memutuskan ingin membangun dokumen XML kompleks saya sebagai JSON. Pertama, bagaimana saya tahu bahwa kelas 'Kompleks' mampu memberikan produk XMLable di tempat pertama dan bagaimana saya mengubahnya untuk menghasilkan objek JSONable? Jawaban cepat: Saya tidak bisa karena saya perlu menggunakan pembangun. Dan kami datang lingkaran penuh ...
David Barker

1
total bs, Builder dirancang untuk membangun objek yang tidak berubah dan dengan kemampuan mengubah cara membangun di masa depan tanpa menyentuh kelas produk
Mickey Tin

6
hmmm? Apakah Anda membaca di suatu tempat di jawaban saya mengatakan untuk apa Builder dirancang? Ini adalah sudut pandang alternatif untuk pertanyaan di atas "Kapan Anda akan menggunakan Pola Builder?", Yang didasarkan pada pengalaman penyalahgunaan pola yang tak terhitung jumlahnya, di mana sesuatu yang lebih sederhana melakukan pekerjaan dengan lebih baik. Semua pola berguna jika Anda tahu kapan dan bagaimana menggunakannya - itulah inti dari mendokumentasikan pola di tempat pertama! Ketika pola terlalu sering digunakan atau lebih buruk - disalahgunakan - maka menjadi anti-pola dalam konteks kode Anda. Sial ...
Pavel Lechev

6

Keuntungan lain dari pembangun adalah bahwa jika Anda memiliki Pabrik, masih ada beberapa kopling dalam kode Anda, karena agar Pabrik berfungsi, ia harus mengetahui semua objek yang dapat dibuatnya . Jika Anda menambahkan objek lain yang bisa dibuat, Anda harus memodifikasi kelas pabrik untuk memasukkannya. Ini terjadi di Pabrik Abstrak juga.

Dengan builder, di sisi lain, Anda hanya perlu membuat builder beton baru untuk kelas baru ini. Kelas direktur akan tetap sama, karena menerima pembangun di konstruktor.

Juga, ada banyak rasa pembangun. Kamikaze Mercenary memberi satu lagi.


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
Anda dapat meningkatkan jawaban Anda dengan dua cara: 1) Jadikan SSCCE. 2) Jelaskan bagaimana ini menjawab pertanyaan.
james.garriss


3

Saya menggunakan builder di perpustakaan perpesanan rumah-tumbuh. Inti pustaka menerima data dari kawat, mengumpulkannya dengan instance Builder, kemudian, setelah Builder memutuskan itu sudah mendapatkan semua yang diperlukan untuk membuat instance Message, Builder.GetMessage () sedang membangun instance pesan menggunakan data yang dikumpulkan dari kawat.



2

Ketika saya ingin menggunakan XMLGregorianCalendar standar untuk XML saya untuk objek marshalling DateTime di Jawa, saya mendengar banyak komentar tentang betapa berat dan rumitnya menggunakannya. Saya mencoba untuk comtrol bidang XML di xs: struct datetime untuk mengelola zona waktu, milidetik, dll.

Jadi saya merancang sebuah utilitas untuk membangun kalender XMLGregorian dari GregorianCalendar atau java.util.Date.

Karena tempat saya bekerja, saya tidak diizinkan membaginya secara online tanpa hukum, tetapi inilah contoh bagaimana klien menggunakannya. Ini abstrak rincian dan filter beberapa implementasi XMLGregorianCalendar yang kurang digunakan untuk xs: datetime.

XMLGregorianCalendarBuilder builder = XMLGregorianCalendarBuilder.newInstance(jdkDate);
XMLGregorianCalendar xmlCalendar = builder.excludeMillis().excludeOffset().build();

Pola ini memang lebih merupakan filter karena menyetel bidang dalam xmlCalendar sebagai tidak terdefinisi sehingga dikecualikan, masih "membuatnya". Saya telah dengan mudah menambahkan opsi lain ke pembuat untuk membuat xs: date, dan xs: struct waktu dan juga untuk memanipulasi zona waktu ketika diperlukan.

Jika Anda pernah melihat kode yang membuat dan menggunakan XMLGregorianCalendar, Anda akan melihat bagaimana ini membuatnya lebih mudah untuk dimanipulasi.


0

Contoh dunia nyata yang hebat adalah untuk digunakan ketika unit menguji kelas Anda. Anda menggunakan pembuat sut (System Under Test).

Contoh:

Kelas:

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;

}

Uji:

    [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);
    }

Pembuat Sutra:

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();
    }
}

Mengapa Anda membutuhkan pembangun dalam hal ini alih-alih hanya menambahkan setter ke CustomAuthenticationServicekelas Anda ?
Laur Ivan

Itu pertanyaan yang bagus @LaurIvan! Mungkin contoh saya agak miskin, tetapi bayangkan Anda tidak dapat mengubah kelas CustomAuthenticationService, pembangun akan menjadi cara yang menarik untuk meningkatkan pembacaan tes unit Anda. Dan saya pikir membuat getter dan setter akan mengekspos bidang Anda, yang akan digunakan hanya untuk tes Anda. Jika Anda ingin sekarang lebih banyak tentang Sut Builder Anda dapat membaca tentang Test Data Builder , yang hampir sama, tetapi untuk suts.
Rafael Miceli
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.