[java] 언제 빌더 패턴을 사용 하시겠습니까?



6 Answers

다음은 Java에서 패턴과 예제 코드 사용에 대한 논쟁의 몇 가지 이유이지만, 이는 Design Patterns 에서 Gang of Four가 다루는 Builder Pattern을 구현 한 것입니다. Java에서 사용하는 이유는 다른 프로그래밍 언어에도 적용됩니다.

조슈아 블로흐 (Joshua Bloch)가 Effective Java, Second Edition :

빌더 패턴은 생성자 또는 정적 팩토리가 소수의 매개 변수 이상을 갖는 클래스를 설계 할 때 좋은 선택입니다.

어느 시점에서 우리는 모든 생성자가 새로운 옵션 매개 변수를 추가하는 생성자 목록이있는 클래스를 발견했습니다.

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에 대한 대안 중 하나는 필수 매개 변수로 생성자를 호출 한 다음 선택적 후 setter를 호출하는 JavaBean 패턴입니다 .

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

따라서 작성하기 쉽고 읽고 이해하기 쉬운 코드가 생성됩니다. 이 예에서 빌드 메소드 는 빌더에서 피자 오브젝트로 복사 된 후에 매개 변수를 점검 하도록 수정 될 수 있으며 유효하지 않은 매개 변수 값이 제공된 경우 IllegalStateException을 던집니다. 이 패턴은 유연하며 나중에 더 많은 매개 변수를 추가하기가 쉽습니다. 생성자에 대해 4 개 또는 5 개의 매개 변수를 사용하려는 경우에만 유용합니다. 즉 , 미래에 더 많은 매개 변수를 추가 할 것으로 의심되는 경우 처음부터 가치가있을 수 있습니다.

Joshua Bloch가 저술 한 Effective Java, 2nd Edition 에서이 주제에 대해 많이 빌 렸습니다. 이 패턴과 다른 효과적인 Java 실습에 대해 자세히 알아 보려면 필자는이를 권장합니다.

Question

Builder Pattern을 사용하는 일반적인 , 실제적인 예 는 무엇입니까? 그것은 무엇을 사나요? 왜 단지 Factory Pattern을 사용하지 않는가?




나는 자생 메시징 라이브러리에 빌더를 사용했다. 라이브러리 코어는 와이어에서 데이터를 수신하여 빌더 인스턴스로 수집 한 다음 빌더가 Message 인스턴스를 작성하는 데 필요한 모든 정보를 가지고 있다고 결정하면 빌더 .GetMessage ()가 수집 한 데이터를 사용하여 메시지 인스턴스를 구성했다. 철사.




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



.NET StringBuilder 클래스는 빌더 패턴의 좋은 예입니다. 이것은 주로 일련의 단계로 문자열을 만드는 데 사용됩니다. ToString ()을 수행 할 때 얻는 최종 결과는 항상 문자열이지만 해당 문자열의 작성은 StringBuilder 클래스의 어떤 함수가 사용되었는지에 따라 다릅니다. 정리하자면 기본 개념은 복잡한 객체를 만들고 구현 방법에 대한 구현 세부 정보를 숨기는 것입니다.




처리 할 옵션이 많을 때 사용합니다. 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);



빌더의 또 다른 이점은 팩토리가있는 경우에도 코드가 커플 링되어 있다는 것입니다. 왜냐하면 팩토리가 작동하기 위해서는 가능한 모든 오브젝트를 알아야하기 때문 입니다. 생성 될 수있는 다른 객체를 추가하는 경우, 그를 포함하도록 팩토리 클래스를 수정해야합니다. 이것은 Abstract Factory에서도 발생합니다.

빌더를 사용하면이 새로운 클래스에 대한 새로운 콘크리트 빌더를 만들어야합니다. 디렉터 클래스는 생성자에서 빌더를 받기 때문에 동일하게 유지됩니다.

또한 건축가의 많은 풍미가 있습니다. 가미 카즈 용병은 또 하나를 준다.




Effective Java에서 설명한대로 내부 빌더 클래스를 생성하는 Generator 메뉴 (Alt + Insert)에 'Builder'동작을 추가하는 IntelliJ IDEA 플러그인 인 InnerBuilder를 확인하십시오.

https://github.com/analytically/innerbuilder




Related