Programing

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

lottogame 2020. 2. 12. 07:55
반응형

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


일부 무엇 일반 , 실제 사례 빌더 패턴을 사용하는가? 무엇을 사나요? 왜 팩토리 패턴을 사용하지 않습니까?


빌더와 팩토리 IMHO의 주요 차이점은 오브젝트를 빌드하기 위해 많은 작업을 수행해야하는 경우 빌더가 유용하다는 것입니다. 예를 들어 DOM을 상상해보십시오. 최종 객체를 얻으려면 많은 노드와 속성을 만들어야합니다. 팩토리는 팩토리가 하나의 메소드 호출 내에서 전체 오브젝트를 쉽게 작성할 수있는 경우에 사용됩니다.

빌더를 사용하는 한 가지 예는 XML 문서를 작성하는 것입니다. 예를 들어 HTML 조각을 작성할 때이 모델을 사용했습니다. 예를 들어 특정 유형의 테이블을 작성하기위한 빌더가 있고 다음과 같은 메소드가있을 수 있습니다 (매개 변수는 표시되지 않음). :

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

그런 다음이 빌더는 HTML을 뱉어 냈습니다. 큰 절차 적 방법을 사용하는 것보다 읽기가 훨씬 쉽습니다.

Wikipedia 에서 빌더 패턴을 확인하십시오 .


다음은 Java에서 패턴 및 예제 코드 사용을 주장하는 몇 가지 이유이지만, 디자인 패턴 에서 Gang of Four가 다루는 빌더 패턴의 구현입니다 . Java로 사용하는 이유는 다른 프로그래밍 언어에도 적용됩니다.

Joshua Bloch는 Effective Java, 2nd 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) { ... }

이를 텔레 스코핑 생성자 패턴이라고합니다. 이 패턴의 문제점은 생성자가 4 또는 5 개의 매개 변수 길이이면 지정된 상황에서 원하는 특정 생성자와 매개 변수 의 필수 순서 를 기억하기어렵다는 것입니다.

Telescoping 생성자 패턴에 대한 한 가지 대안 은 필수 매개 변수를 사용하여 생성자를 호출 한 후 다음과 같은 선택적 세터를 호출하는 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();

결과적으로 작성하기 쉽고 읽기 쉽고 이해하기 쉬운 코드가 만들어집니다. 이 예제에서, 빌드 메소드는 매개 변수가 빌더에서 Pizza 오브젝트로 복사 된 후 매개 변수를 점검 하고 올바르지 않은 매개 변수 값이 제공된 경우 IllegalStateException을 발생 시키도록 수정 될 수 있습니다 . 이 패턴은 융통성이 있으며 향후 더 많은 매개 변수를 추가하기가 쉽습니다. 생성자에 대해 4 개 또는 5 개 이상의 매개 변수가있는 경우에만 유용합니다. , 앞으로 더 많은 매개 변수를 추가 할 것으로 의심되면 처음에는 가치가있을 수 있습니다.

나는 Joshua Bloch의 Effective Java, 2nd Edition 책에서이 주제에 대해 많은 것을 빌렸다 . 이 패턴과 다른 효과적인 Java 관행에 대해 더 배우려면 강력히 권장합니다.


식당을 생각해 보자. "오늘의 식사"작성은 공장 패턴입니다. 부엌에 "오늘의 식사를주세요"라고 말하면 부엌 (공장)은 숨겨진 기준에 따라 어떤 객체를 생성할지 결정하기 때문입니다.

사용자 정의 피자를 주문하면 빌더가 나타납니다. 이 경우, 웨이터는 요리사 (빌더)에게 "피자, 치즈, 양파, 베이컨을 넣습니다!"라고 말합니다. 따라서 빌더는 생성 된 오브젝트가 가져야하는 속성을 노출하지만 설정 방법을 숨 깁니다.


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

Microsoft MVC 프레임 워크를 거치면서 빌더 패턴에 대해 생각했습니다. ControllerBuilder 클래스에서 패턴을 발견했습니다. 이 클래스는 컨트롤러 팩토리 클래스를 반환하고 콘크리트 컨트롤러를 만드는 데 사용됩니다.

빌더 패턴을 사용할 때의 장점은 자신의 팩토리를 만들어 프레임 워크에 연결할 수 있다는 것입니다.

@Tetha에는 피자를 제공하는 이탈리아 사람이 운영하는 식당 (프레임 워크)이 있습니다. 피자를 준비하기 위해 이탈리아 사람 (오브젝트 빌더)은 피자베이스 (기본 클래스)와 함께 Owen (공장)을 사용합니다.

이제 인도 사람이 이탈리아 사람으로부터 식당을 인수합니다 피자 대신 인도 식당 (프레임 워크) 서버 dosa. 인도 사람 (객체 빌더)을 준비하기 위해 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;
   }
}

빌더의 또 다른 장점은 팩토리가있는 경우 코드에 여전히 커플 링이 있다는 것 입니다. 팩토리가 작동하려면 작성할 수있는 모든 오브젝트를 알아야하기 때문 입니다. 만들 수있는 다른 객체를 추가하는 경우 팩토리 클래스를 수정하여 객체를 포함시켜야합니다. 이것은 추상 팩토리에서도 발생합니다.

반면에 빌더를 사용하면이 새 클래스에 대한 새 콘크리트 빌더를 작성하기 만하면됩니다. director 클래스는 생성자에서 빌더를 수신하므로 동일하게 유지됩니다.

또한 많은 풍미가 있습니다. 카미카제 용병이 또 하나를 준다.


나는 항상 작성기 패턴을 다루기 힘들고, 눈에 잘 띄지 않으며, 경험이 부족한 프로그래머가 자주 사용하는 것으로 싫어했습니다. 초기화 후 단계 가 필요한 일부 데이터에서 개체를 조립해야하는 경우에만 의미가있는 패턴입니다 (즉, 모든 데이터가 수집되면 무언가를 수행하십시오). 대신, 시간 빌더의 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");

이제 우리는 자체 초기화를 관리하고 훨씬 더 우아하다는 것을 제외하고 빌더와 거의 동일한 작업을 수행하는 깔끔한 단일 클래스를 가지고 있습니다.


이전 답변을 바탕으로 (실제로 의도 된) 훌륭한 실제 예는 Groovy 의 내장 지원입니다 Builders.

페이지 빌더 에서 그루비 문서


/// <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 인스턴스를 작성하는 데 필요한 모든 것을 얻었 으면 Builder.GetMessage ()는 데이터를 수집하여 데이터 인스턴스를 작성했습니다. 철사.


Java에서 DateTime의 마샬링을 반대하기 위해 XML에 표준 XMLGregorianCalendar를 사용하고 싶었을 때 그것을 사용하는 것이 얼마나 무겁고 번거로운 지에 대한 많은 의견을 들었습니다. 시간대, 밀리 초 등을 관리하기 위해 xs : datetime 구조체의 XML 필드를 분석하려고했습니다.

그래서 GregorianCalendar 또는 java.util.Date에서 XMLGregorian 캘린더를 빌드하는 유틸리티를 설계했습니다.

내가 일하는 곳 때문에 법적으로 온라인 공유가 허용되지 않지만 클라이언트가이를 사용하는 방법의 예는 다음과 같습니다. 세부 정보를 추상화하고 xs : datetime에 덜 사용되는 XMLGregorianCalendar의 일부 구현을 필터링합니다.

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

이 패턴은 xmlCalendar의 필드를 정의되지 않은 필드로 설정하여 제외되므로 여전히 "빌드"하는 필터입니다. xs : date 및 xs : time 구조체를 작성하고 필요할 때 시간대 오프셋을 조작하기 위해 빌더에 다른 옵션을 쉽게 추가했습니다.

XMLGregorianCalendar를 작성하고 사용하는 코드를 본 적이 있다면 이것이 조작하기가 훨씬 쉬워 졌음을 알게 될 것입니다.


유효 Java에 설명 된대로 내부 빌더 클래스를 생성하는 생성 메뉴 (Alt + Insert)에 '빌더'조치를 추가하는 IntelliJ IDEA 플러그인 InnerBuilder를 확인하십시오.

https://github.com/analytically/innerbuilder


실제 사례는 클래스를 테스트 할 때 사용하는 것입니다. 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 Builder :

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

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



반응형