Programing

Builder Pattern을 언제 사용하시겠습니까?

c10106 2022. 5. 19. 22:12
반응형

Builder Pattern을 언제 사용하시겠습니까?

Builder Pattern을 사용하는 일반적실제 예는 무엇인가?그게 너에게 무엇을 사주는가?공장 패턴만 사용하는 게 어때?

아래는 자바에서 패턴과 예제 코드의 사용을 주장하는 몇 가지 이유지만, 디자인 패턴의 4강에서 다루는 빌더 패턴의 구현이다.당신이 그것을 자바에서 사용하는 이유는 다른 프로그래밍 언어에도 적용된다.

Joshua Bloch가 Effective 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) { ... }

이것을 텔레스코핑 생성자 패턴이라고 한다.이 패턴의 문제는 일단 생성자가 4 또는 5 매개변수 길이에 도달하면 매개 변수의 필요한 순서뿐만 아니라 특정 상황에서 원하는 특정 생성자를 기억하기가 어려워진다는 것이다.

Tescoping Constructor Pattern에 대한 대안으로 JavaBean Pattern을 들 수 있다. 이 경우 필수 매개 변수를 가진 생성자를 호출한 다음 다음 옵션 설정자를 호출한다.

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

피자는 불변성이며 파라미터 값은 모두곳에 있다는 점에 유의한다.Builder의 setter 메서드는 Builder 개체를 반환하기 때문에 체인으로 묶일있다.

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

이것은 쓰기 쉽고 읽고 이해하는 매우 쉬운 코드로 귀결된다.이 예에서는 빌더에서 피자 객체로 파라미터를 복사한 후 파라미터를 확인하고, 유효하지 않은 파라미터 값이 제공된 경우 FrailyStateException을 던지도록 빌드 방법을 수정할 수 있다.이 패턴은 유연하고 앞으로 더 많은 파라미터를 추가하기 쉽다.이것은 당신이 건설업자에 대해 4개 또는 5개 이상의 파라미터를 가질 경우에만 정말로 유용하다.그렇긴 하지만, 만약 당신이 미래에 더 많은 파라미터를 추가할 것이라고 의심한다면, 애당초 가치 있는 일일지도 모른다.

나는 이 주제에 대해 조슈아 Bloch의 "Effective Java, 2판"이라는 책에서 많이 빌렸다.이 패턴과 다른 효과적인 Java 연습에 대해 더 많이 알고 싶다면 나는 그것을 적극 추천한다.

식당을 고려하다.'오늘의 식사'의 창조는 공장 패턴으로, 부엌에 '오늘의 식사를 가져다 달라'고 말하고 부엌(공장)이 숨겨진 기준에 따라 어떤 대상을 생성할지 결정하기 때문이다.

맞춤 피자를 주문하면 제작자가 나타난다.이 경우 웨이터는 주방장(빌더)에게 "피자가 필요해; 거기에 치즈, 양파, 베이컨을 넣어!"라고 말한다.따라서, 작성자는 생성된 객체가 가져야 하는 속성을 노출시키지만, 설정 방법은 숨긴다.

빌더와 공장 IMHO의 중요한 차이점은 빌더가 물체를 만들기 위해 많은 것을 해야 할 때 유용하다는 것이다.예를 들어 DOM을 상상해 보십시오.최종 객체를 얻으려면 많은 노드와 속성을 만들어야 한다.공장은 공장이 하나의 메서드 호출 내에서 전체 물체를 쉽게 만들 수 있을 때 사용된다.

빌더를 사용하는 예로는 XML 문서를 작성하는 것이 있는데, HTML 파편을 작성할 때 이 모델을 사용했는데, 예를 들어, 특정 유형의 테이블을 빌더로 작성할 때 다음과 같은 방법을 사용할 수 있다(파라미터는 표시되지 않음).

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

이 건축가는 나를 위해 HTML을 뱉어내곤 했다.이것은 큰 절차적 방법을 거치는 것보다 훨씬 읽기 쉽다.

위키피디아에서 Builder Pattern을 확인하십시오.

.NET StringBuilder 클래스는 빌더 패턴의 좋은 예다.그것은 대부분 일련의 단계에서 줄을 만드는 데 사용된다.ToString()을 하는 최종 결과는 항상 문자열이지만 StringBuilder 클래스에서 사용된 함수에 따라 해당 문자열의 생성이 달라진다.요약하자면, 기본 아이디어는 복잡한 객체를 구축하고, 그것이 어떻게 구축되고 있는지에 대한 구현 세부사항을 숨기는 것이다.

다중 스레드 문제를 위해서는 각 스레드에 대해 쌓아야 할 복잡한 물체가 필요했다.개체는 처리 중인 데이터를 나타내며 사용자 입력에 따라 변경될 수 있다.

우리가 대신 공장을 이용할 수 있을까?

왜 안 그랬어?빌더가 더 말이 되는 것 같아.

공장은 동일한 기본 유형(동일한 인터페이스 또는 기본 클래스를 구현)인 다른 유형의 객체를 만드는 데 사용된다.

건설업자들은 같은 종류의 물건을 반복해서 짓지만, 시공은 동적이어서 런타임에 변경할 수 있다.

나는 항상 빌더 패턴이 다루기 힘들고, 다루기 힘들고, 경험이 적은 프로그래머들에 의해 종종 남용되는 것을 싫어했다.초기화단계를 필요로 하는 일부 데이터에서 객체를 조립해야 하는 경우에만 의미가 있는 패턴이다(즉, 모든 데이터가 수집된 후 - 이 데이터로 뭔가를 해야 함).대신에, 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");

이제 우리는 그 자체의 초기화를 관리하고 훨씬 더 우아하다는 점만 빼면 건설업자와 거의 같은 일을 하는 깔끔한 싱글 클래스를 갖게 되었다.

당신은 처리할 수 있는 많은 선택권이 있을 때 그것을 사용한다.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)는 피자 베이스(베이스 클래스)로 오웬(팩토리)을 이용한다.

이제 인도 남자는 이탈리아 남자로부터 식당을 물려받는다.피자 대신 인도 레스토랑(프레임워크) 서버 도사.도사 인도남(객체조성자)을 준비하기 위해 프라이팬(공장)을 마이다(베이스 클래스)와 함께 사용한다.

시나리오를 보면, 음식은 다르지만, 음식이 준비되는 방식은 다르지만, 같은 레스토랑에서(같은 틀 아래)레스토랑은 중국, 멕시코 또는 어떤 요리를 지원할 수 있는 방식으로 지어져야 한다.프레임워크 내부의 오브젝트 빌더는 원하는 종류의 요리를 간편하게 플러그인할 수 있다.예를 들면

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

이전의 답변(pun 의도된 의도된)을 바탕으로 구축된 우수한 실제 예는 Groovy의 지원 하에 구축된 것이다.Builders.

Groovy 설명서에서 시공자 참조

건설업자의 또 다른 이점은, 만약 당신이 공장을 가지고 있다면, 당신의 코드에 여전히 약간의 결합이 있다는 것이다. 왜냐하면 공장이 작동하기 위해서는, 그것은 그것이 창조할 수 있는 모든 물체를 알아야 하기 때문이다.생성될 수 있는 다른 개체를 추가하면 그를 포함하도록 공장 클래스를 수정해야 한다.이것은 추상 공장에서도 일어난다.

반면에 건축업자와는 이 새로운 클래스를 위한 새로운 콘크리트 건축업자를 만들면 된다.건설업자가 건설업자에게서 건설업자를 받기 때문에 감독반은 그대로 유지될 것이다.

또한 건축가에는 여러 가지 맛이 있다.가미카제 용병이 또 하나를 준다.

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

나는 집에서 기른 메시징 라이브러리에서 빌더를 사용했다.라이브러리 코어는 와이어로부터 데이터를 수신하여 Builder 인스턴스로 데이터를 수집하고, Builder가 Message 인스턴스(instance)를 만드는 데 필요한 모든 것을 확보했다고 결정했을 때,GetMessage()는 와이어에서 수집된 데이터를 사용하여 메시지 인스턴스를 구성하고 있었다.

자바에서 다테타임의 정비를 반대하기 위해 XML에 표준 XMLGregorianCalendar를 사용하고 싶었을 때, 그것을 사용하는 것이 얼마나 무겁고 번거로운지에 대한 코멘트를 많이 들었다.xs:datetime 구조에서 XML 필드를 comtrol하여 표준 시간대, 밀리초 등을 관리하려고 했다.

그래서 나는 그레고리칼렌다르 또는 자바.util에서 XMLGregorian 캘린더를 만드는 유틸리티를 설계했다.날짜

내가 일하는 곳 때문에 합법적이지 않으면 온라인에서 공유할 수 없지만, 여기 고객이 그것을 어떻게 사용하는지 보여주는 예가 있다.세부사항을 추상화하고 xs:datetime에 덜 사용되는 XMLGregorianCalendar 구현의 일부를 필터링한다.

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

xmlCalendar의 필드를 정의되지 않은 필드로 설정하여 제외할 경우 이 패턴은 필터에 가깝지만 여전히 "구축"된다.나는 xs:date 및 xs:time 구조물을 만들고 필요할 때 시간대 오프셋을 조작하기 위해 쉽게 다른 옵션을 빌더에 추가했다.

XMLGregorianCalendar를 생성하고 사용하는 코드를 본 적이 있다면, 이것이 어떻게 조작을 훨씬 쉽게 했는지 알 수 있을 것이다.

참조URL: https://stackoverflow.com/questions/328496/when-would-you-use-the-builder-pattern

반응형