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




게임 design (13)

필자는 항상 빌더 패턴을 다루기 힘들고 눈에 띄지 않으며 자주 경험이 부족한 프로그래머가 남용하는 것으로 싫어했습니다. 초기화 된 단계 (즉, 모든 데이터가 수집 된 후에는이를 사용하여 작업)가 필요한 일부 데이터에서 객체를 어셈블해야하는 경우에만 해당 패턴입니다. 대신, 99 %의 시간에 빌더는 단순히 클래스 멤버를 초기화하는 데 사용됩니다.

이런 경우 단순히 withXyz(...) 유형 setter를 클래스 내부에 선언하고 그 클래스 자체에 대한 참조를 반환하도록하는 것이 훨씬 좋습니다.

이걸 고려하세요:

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

이제 우리는 자체 초기화를 관리하는 청결한 단일 클래스를 사용했으며 훨씬 더 우아한 점을 제외하고는 빌더와 거의 동일한 작업을 수행합니다.

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


Microsoft MVC 프레임 워크를 진행하면서 빌더 패턴에 대해 생각해 봤습니다. ControllerBuilder 클래스의 패턴을 발견했습니다. 이 클래스는 컨트롤러 팩토리 클래스를 반환하며,이 클래스는 구체적인 컨트롤러를 작성하는 데 사용됩니다.

장점은 빌더 패턴을 사용하여 볼 수 있습니다, 당신은 자신의 공장을 만들 수 있으며 프레임 워크에 연결합니다.

@Tetha, 피자를 제공하는 이탈리아 사람이 운영하는 레스토랑 (Framework)이있을 수 있습니다. 피자를 준비하기 위해 이탈리아 사람 (Object Builder)은 피자베이스 (기본 클래스)와 함께 Owen (공장)을 사용합니다.

이제 인도 사람이 이탈리아 사람에게서 레스토랑을 인수합니다. 인디언 레스토랑 (프레임 워크) 서버는 피자 대신 도사입니다. 인도인 인 dosa를 준비하기 위해 Maida (기본 클래스)와 함께 Frying Pan (Factory)을 사용합니다.

당신이 시나리오를 보았을 때, 음식은 다르고, 음식이 준비되는 방식은 다르지만 같은 레스토랑에서 (동일한 틀 아래). 레스토랑은 중국, 멕시코 또는 모든 요리를 지원할 수있는 방식으로 만들어야합니다. 프레임 워크 내의 오브젝트 빌더는 원하는 종류의 플러그인을 쉽게 플러그인 할 수있게합니다. 예를 들면

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

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

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

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

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

이 빌더는 나를 위해 HTML을 뱉어 냈다. 이것은 많은 절차 적 방법을 통해 읽은 후 훨씬 쉽게 읽을 수 있습니다.

Wikipedia의 작성자 패턴을 확인하십시오.


훌륭한 실세계 예제는 클래스를 유닛 테스트 할 때 사용하는 것입니다. sut (System Under Test) 빌더를 사용합니다.

예:

수업:

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

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


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

그래서 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를 생성하고 사용하는 코드를 본 적이 있다면 어떻게 조작하는 것이 더 쉬운지 알 수 있습니다.


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

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

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


레스토랑을 생각해보십시오. "오늘의 식사"의 생성은 공장 패턴입니다. 왜냐하면 당신은 부엌에 "오늘의 식사를 가져 오십시오"라고 말하고 부엌 (공장)은 숨겨진 기준에 따라 어떤 객체를 생성할지 결정하기 때문입니다.

맞춤 피자를 주문하면 작성 도구가 나타납니다. 이 경우 웨이터가 요리사에게 "피자가 필요합니다. 치즈, 양파 및 베이컨을 추가하십시오!"라고 알려줍니다. 따라서 빌더는 생성 된 객체가 가져야하는 속성을 노출하지만 설정 방법을 숨 깁니다.


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

https://github.com/analytically/innerbuilder


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


멀티 스레드 문제의 경우 각 스레드에 대해 복잡한 객체가 필요했습니다. 객체는 처리되는 데이터를 나타내며 사용자 입력에 따라 변경 될 수 있습니다.

대신 공장을 사용할 수 있습니까? 예

왜 우리가하지 않았습니까? 빌더는 내가 생각하기에 더 합리적이다.

팩토리는 같은 기본 유형 (동일한 인터페이스 또는 기본 클래스를 구현) 인 다른 유형의 객체를 만드는 데 사용됩니다.

빌더는 같은 유형의 객체를 반복해서 작성하지만 구조는 동적이므로 런타임에 변경할 수 있습니다.


"Functional Programming Patterns- in Scala and Clojure" 라는 새로운 2013 년작 Michael.B. Linn은 GoF 패턴을 위해 많은 경우 대체품을 비교하고 제공하는 괜찮은 일을하며 '꼬리 재귀', '메모 작성', '지연 시퀀스'등과 같은 새로운 기능 패턴에 대해서도 논의합니다.

이 책은 Amazon에서 사용할 수 있습니다. 나는 수십 년에 걸쳐 OO 배경에서 왔을 때 매우 유익하고 고무적인 것을 발견했다.







java design-patterns builder