답변:
빌더와 팩토리 IMHO의 주요 차이점은 오브젝트를 빌드하기 위해 많은 작업을 수행해야하는 경우 빌더가 유용하다는 것입니다. 예를 들어 DOM을 상상해보십시오. 최종 객체를 얻으려면 많은 노드와 속성을 만들어야합니다. 팩토리는 팩토리가 하나의 메소드 호출 내에서 전체 오브젝트를 쉽게 작성할 수있는 경우에 사용됩니다.
빌더를 사용하는 한 가지 예는 XML 문서를 작성하는 것입니다. 예를 들어 HTML 조각을 작성할 때이 모델을 사용했습니다. 예를 들어 특정 유형의 테이블을 작성하기위한 빌더가 있고 다음과 같은 메소드가있을 수 있습니다 (매개 변수는 표시되지 않음). :
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
그런 다음이 빌더는 HTML을 뱉어 냈습니다. 큰 절차 적 방법을 사용하는 것보다 읽기가 훨씬 쉽습니다.
다음은 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 관행에 대해 더 배우려면 강력히 권장합니다.
new Pizza.Builder(12).cheese().pepperoni().bacon().build();
Pizza.Builder(12).cheese().pepperoni().bacon().build();
, 당신은 당신의 코드를 다시 컴파일해야하거나 일부 페퍼로니 필요하면 불필요한 로직을 피자. 최소한 @Kamikaze 용병과 같이 매개 변수화 된 버전을 제공해야합니다. Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();
. 그런 다음 다시 우리는 단위 테스트를하지 않습니다.
식당을 생각해 보자. "오늘의 식사"를 만드는 것은 공장 패턴입니다. 부엌에 "오늘의 식사를주세요"라고 말하면 부엌 (공장)은 숨겨진 기준에 따라 어떤 객체를 생성할지 결정하기 때문입니다.
사용자 정의 피자를 주문하면 빌더가 나타납니다. 이 경우, 웨이터는 요리사 (빌더)에게 "피자, 치즈, 양파, 베이컨을 넣습니다!"라고 말합니다. 따라서 빌더는 생성 된 오브젝트가 가져야하는 속성을 노출하지만 설정 방법을 숨 깁니다.
.NET StringBuilder 클래스는 빌더 패턴의 좋은 예입니다. 주로 일련의 단계에서 문자열을 만드는 데 사용됩니다. ToString ()을 수행하는 최종 결과는 항상 문자열이지만 해당 문자열의 생성은 사용 된 StringBuilder 클래스의 함수에 따라 다릅니다. 요약하자면, 기본 아이디어는 복잡한 객체를 구축하고 구축 방법에 대한 구현 세부 사항을 숨기는 것입니다.
b.append(...).append(...)
최종적으로 호출하기 전에 연결할 수있는 방법에 주목하십시오 toString()
. 인용 : infoq.com/articles/internal-dsls-java
처리 할 옵션이 많을 때 사용합니다. 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;
}
}
나는 항상 빌더 패턴을 다루기 힘들고, 눈에 잘 띄지 않으며, 경험이 부족한 프로그래머가 자주 사용하는 것으로 싫어했습니다. 초기화 후 단계 가 필요한 일부 데이터에서 개체를 조립해야하는 경우에만 의미가있는 패턴입니다 (즉, 모든 데이터가 수집되면 무언가를 수행하십시오). 대신, 시간 빌더의 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");
이제 우리는 자체 초기화를 관리하고 훨씬 더 우아하다는 것을 제외하고 빌더와 거의 동일한 작업을 수행하는 깔끔한 단일 클래스를 가지고 있습니다.
/// <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);
}
}
이전 답변을 바탕으로 (실제로 의도 된) 훌륭한 실제 예는 Groovy 의 내장 지원입니다 Builders
.
유효 Java에 설명 된대로 내부 빌더 클래스를 생성하는 생성 메뉴 (Alt + Insert)에 '빌더'조치를 추가하는 IntelliJ IDEA 플러그인 InnerBuilder를 확인하십시오.
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를 작성하고 사용하는 코드를 본 적이 있다면 이것이 조작하기가 훨씬 쉬워 졌음을 알게 될 것입니다.
실제 사례는 클래스를 테스트 할 때 사용하는 것입니다. 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();
}
}
CustomAuthenticationService
클래스에 세터를 추가하는 대신 빌더가 필요한 이유는 무엇 입니까?