Kiedy użyjesz wzoru budowniczego? [zamknięte]

Jakie są niektóre Wspólne, przykłady użycia wzorca Builder w świecie rzeczywistym? Co ci to kupi? Dlaczego po prostu nie użyć wzoru fabrycznego?

Author: Flow, 2008-11-30

15 answers

Kluczową różnicą między budowniczym a fabrycznym IMHO jest to, że budowniczy jest przydatny, gdy trzeba zrobić wiele rzeczy, aby zbudować obiekt. Na przykład wyobraź sobie DOM. Musisz utworzyć wiele węzłów i atrybutów, aby uzyskać ostateczny obiekt. Fabryka jest używana, gdy fabryka może łatwo wytworzyć cały obiekt w ramach jednego wywołania metody.

Jednym z przykładów użycia Buildera jest budowanie dokumentu XML, używałem tego modelu podczas budowania fragmentów HTML na przykład Builder do budowania określonego typu tabeli i może mieć następujące metody (parametry NIE są pokazane):

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

Ten budowniczy wyplułby mi HTML. Jest to o wiele łatwiejsze do odczytania, a następnie przechodzenie przez dużą metodę proceduralną.

Zobacz Wzór budowniczego na Wikipedii.

 230
Author: JoshBerke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-03 21:24:30

Poniżej kilka powodów przemawiających za użyciem wzorca i przykładowego kodu w Javie, ale jest to implementacja wzorca konstruktora objętego gangiem czterech w wzorcach projektowych. Powody, dla których można go używać w Javie, odnoszą się również do innych języków programowania.

Jak stwierdza Joshua Bloch w Effective Java, 2nd Edition :

Wzorzec konstruktora jest dobrym wyborem przy projektowaniu klas, których konstruktorzy lub statyczne fabryki ma więcej niż garść parametrów.

W pewnym momencie wszyscy napotkaliśmy klasę z listą konstruktorów, gdzie każdy dodatek dodaje nowy parametr opcji:

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

Jest to tzw. wzorzec konstruktora teleskopowego. problem z tym wzorcem polega na tym, że gdy konstruktory mają 4 lub 5 parametrów, staje się trudno zapamiętać wymaganą kolejność parametrów jak również jaki konkretny konstruktor chcesz w danej sytuacji.

Jeden alternatywny musisz wzorzec konstruktora teleskopowego to JavaBean wzorzec gdzie wywołujesz konstruktor z obowiązkowymi parametrami, a następnie wywołujesz dowolne opcjonalne ustawiacze po:

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

Problem polega na tym, że ponieważ obiekt jest tworzony przez kilka wywołań, może on znajdować się w niespójnym stanie podczas jego budowy. wymaga to również dużo dodatkowego wysiłku, aby zapewnić wątek bezpieczeństwo.

Lepszą alternatywą jest użycie wzoru konstruktora.

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

Zauważ, że Pizza jest niezmienna, a wartości parametrów są w jednym miejscu . Ponieważ metody Settera Buildera zwracają obiekt Builder, są one w stanie być połączone łańcuchem .

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

W rezultacie powstaje kod, który jest łatwy do napisania i bardzo łatwy do odczytania i zrozumienia. w tym przykładzie metoda build może zostać zmodyfikowana w celu sprawdzenia parametrów po skopiowaniu ich z konstruktora do obiektu Pizza i wyrzuć wyjątek IllegalStateException, jeśli Podano nieprawidłową wartość parametru. ten wzór jest elastyczny i łatwo jest dodać do niego więcej parametrów w przyszłości. Jest to naprawdę przydatne tylko wtedy, gdy masz zamiar mieć więcej niż 4 lub 5 parametrów dla konstruktora. To powiedziawszy, może warto w pierwszej kolejności , jeśli podejrzewasz, że możesz dodać więcej parametrów w przyszłości.

Pożyczyłem mocno na ten temat z książki Effective Java, 2nd Edition autorstwa Joshuy Bloch. Aby dowiedzieć się więcej na temat tego wzorca i innych skutecznych praktyk Java Gorąco go polecam.

 930
Author: Aaron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-11 12:39:16

Rozważ restaurację. Tworzenie "today' s meal "jest wzorcem fabrycznym, ponieważ mówisz kuchni" get me today 's meal", a kuchnia (fabryka) decyduje, jaki obiekt wygenerować, na podstawie ukrytych kryteriów.

Budowniczy pojawi się, jeśli zamówisz niestandardową pizzę. W tym przypadku kelner mówi kucharzowi (budowniczemu) "potrzebuję pizzy; Dodaj do niej ser, cebulę i boczek!"W ten sposób konstruktor ujawnia atrybuty, które powinien posiadać wygenerowany obiekt, ale ukrywa sposób ich ustawienia.

 299
Author: Tetha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-01 18:20:00

Klasa. NET StringBuilder jest świetnym przykładem wzorca Buildera. Jest najczęściej używany do tworzenia ciągu w szeregu kroków. Końcowy wynik działania ToString () jest zawsze ciągiem znaków, ale tworzenie tego łańcucha różni się w zależności od tego, jakie funkcje w klasie StringBuilder zostały użyte. Podsumowując, podstawową ideą jest budowanie złożonych obiektów i ukrywanie szczegółów implementacji tego, jak jest budowany.

 16
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-20 01:46:00

W przypadku problemu wielowątkowego potrzebowaliśmy złożonego obiektu do zbudowania dla każdego wątku. Obiekt reprezentował przetwarzane dane i mógł się zmieniać w zależności od danych wprowadzonych przez użytkownika.

Czy zamiast tego moglibyśmy użyć fabryki? Tak

Dlaczego tego nie zrobiliśmy? Budowniczy ma chyba większy sens.

Fabryki są używane do tworzenia różnych typów obiektów, które są tego samego typu podstawowego (implementują ten sam interfejs lub klasę bazową).

Budowniczowie budują ten sam typ obiektu w kółko, ale konstrukcja jest dynamiczna, więc można ją zmieniać w czasie pracy.

 9
Author: Cameron MacFarland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-30 05:51:17

Używasz go, gdy masz wiele opcji do rozwiązania. Pomyśl o takich rzeczach jak jmock:

m.expects(once())
    .method("testMethod")
    .with(eq(1), eq(2))
    .returns("someResponse");
Wydaje się dużo bardziej naturalne i jest...możliwe.

Istnieje również XML building, string building i wiele innych rzeczy. Wyobraź sobie, że java.util.Map postawił jako budowniczego. Możesz robić takie rzeczy:

Map<String, Integer> m = new HashMap<String, Integer>()
    .put("a", 1)
    .put("b", 2)
    .put("c", 3);
 6
Author: Dustin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-30 05:50:14

Przechodząc przez Microsoft MVC framework, pomyślałem o builder pattern. Natknąłem się na wzór w klasie ControllerBuilder. Klasa ta ma zwracać klasę fabryczną kontrolera, która jest następnie używana do budowy betonowego kontrolera.

Zaletą, jaką widzę w użyciu builder pattern, jest to, że możesz stworzyć własną fabrykę i podłączyć ją do frameworka.

@Tetha, może być Restauracja (Framework) prowadzona przez Włocha, która serwuje pizzę. W celu przygotowania Pizza włoski facet (Budowniczy obiektów) używa Owena (fabryka) z bazą pizzy (klasa bazowa). Teraz Indianin przejmuje restaurację od Włocha. Restauracja indyjska (Framework) serwery dosa zamiast pizzy. W celu przygotowania Dosa Indianin (obiekt builder) używa patelni (Factory) z klasą Maida (base class)

Jeśli spojrzeć na scenariusz, jedzenie jest inne, sposób przygotowania jedzenia jest inny, ale w tej samej restauracji (w tych samych ramach). Restauracja powinna być zbudowana w takim sposób, że może wspierać chińską, meksykańską lub dowolną kuchnię. Obiekt builder wewnątrz framework ułatwia wtyczki rodzaj kuchni chcesz. na przykład

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;
   }
}
 6
Author: Nitin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-21 11:57:42

Kolejną zaletą Buildera jest to, że jeśli masz fabrykę, nadal istnieje pewne sprzężenie w kodzie, ponieważ Aby Fabryka działała, musi znać wszystkie obiekty, które może utworzyć. Jeśli dodasz kolejny obiekt, który może zostać utworzony, będziesz musiał zmodyfikować klasę factory, aby go włączyć. Dzieje się tak również w fabryce abstrakcji.

Z budowniczym, z drugiej strony, musisz tylko stworzyć nowy budowniczy betonu dla tej nowej klasy. Klasa reżyserska pozostanie taki sam, ponieważ otrzymuje konstruktora w konstruktorze.

Istnieje również wiele smaków budowniczego. Najemnik Kamikaze daje kolejnego.

 5
Author: Lino Rosa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-24 00:20:42
/// <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);
    }
}
 5
Author: Raman Zhylich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-16 12:55:36

Bazując na poprzednich odpowiedziach (Kalambury), doskonałym przykładem w świecie rzeczywistym jest Groovy wbudowana obsługa Builders.

Zobacz budowniczych w Groovy dokumentacji

 5
Author: Ken Gentle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-29 16:41:59

Zawsze nie lubiłem wzorca konstruktora jako czegoś nieporęcznego, natrętnego i bardzo często nadużywanego przez mniej doświadczonych programistów. Jest to wzorzec, który ma sens tylko wtedy, gdy trzeba złożyć obiekt z niektórych danych, które wymagają kroku po inicjalizacji (tzn. po zebraniu wszystkich danych-zrób coś z nim). Zamiast tego, w 99% czasu Budowniczowie są po prostu używane do inicjalizacji członków klasy.

W takich przypadkach o wiele lepiej jest po prostu zadeklarować withXyz(...) setery typu wewnątrz klasy i sprawiają, że zwracają odniesienie do siebie.

Rozważ to:

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

Teraz mamy schludną, pojedynczą klasę, która zarządza własną inicjalizacją i wykonuje prawie to samo zadanie co konstruktor, z tym, że jest znacznie bardziej elegancka.

 3
Author: Pavel Lechev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-07 13:16:29

Używałem Buildera w domowej bibliotece komunikatorów. Rdzeń biblioteki odbierał dane z przewodu, zbierał je za pomocą instancji Builder, a następnie, gdy Builder zdecydował, że ma wszystko, co potrzebne do utworzenia instancji Message, Builder.GetMessage() konstruowała instancję wiadomości na podstawie danych zebranych z przewodu.

 2
Author: wasker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-30 18:05:42

Kiedy chciałem użyć standardowego XMLGregorianCalendar dla mojego XML do obiektowego ustawiania DateTime w Javie, słyszałem wiele komentarzy na temat tego, jak ciężkie i uciążliwe było jego użycie. Próbowałem połączyć pola XML w strukturach XS: datetime, aby zarządzać strefą czasową, milisekundami itp.

Więc zaprojektowałem narzędzie do budowania kalendarza Xmlgregorian z GregorianCalendar lub Javy.util.Data.

Ze względu na to, gdzie pracuję, nie mogę udostępniać tego w Internecie bez legalnego, ale oto przykład, jak klient z niego korzysta. Pobiera szczegóły i filtruje niektóre implementacje xmlgregoriancalendar, które są mniej używane w xs: datetime.

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

Ten wzorzec jest bardziej filtrem, ponieważ ustawia pola w xmlcalendarze jako niezdefiniowane, więc są one wykluczone, nadal je "buduje". Łatwo dodałem inne opcje do konstruktora, aby utworzyć strukturę XS:date i XS:time, a także manipulować przesunięciami stref czasowych w razie potrzeby.

If you ' ve ever seen kod, który tworzy i używa XMLGregorianCalendar, można zobaczyć, jak to znacznie ułatwiło manipulowanie.

 2
Author: John Brown,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-04 11:01:57

Sprawdź InnerBuilder, wtyczkę IntelliJ IDEA, która dodaje akcję 'Builder' do menu Generate (Alt+Insert), która generuje wewnętrzną klasę builder, jak opisano w Effective Java

Https://github.com/analytically/innerbuilder

 1
Author: analytically,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-20 08:43:03

Świetnym przykładem ze świata rzeczywistego jest Testowanie jednostek klas. Używasz konstruktorów SUT (System Under Test).

Przykład:

Klasa:

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:

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

Sutener:

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();
    }
}
 0
Author: Rafael Miceli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-16 17:34:49