Oluşturucu Desenini kullanmanın bazı yaygın , gerçek dünya örnekleri nelerdir? Seni ne satın alıyor? Neden sadece bir Fabrika Deseni kullanmıyorsunuz?
Oluşturucu Desenini kullanmanın bazı yaygın , gerçek dünya örnekleri nelerdir? Seni ne satın alıyor? Neden sadece bir Fabrika Deseni kullanmıyorsunuz?
Yanıtlar:
Bir yapıcı ve fabrika IMHO arasındaki temel fark, bir yapıcıyı bir nesne oluşturmak için birçok şey yapmanız gerektiğinde yararlıdır. Örneğin bir DOM düşünün. Nihai nesnenizi elde etmek için bol miktarda düğüm ve nitelik oluşturmanız gerekir. Fabrika, tek bir yöntem çağrısında tüm nesneyi kolayca oluşturabildiğinde fabrika kullanılır.
Oluşturucu kullanmanın bir örneği, bir XML belgesi oluşturmaktır, HTML parçaları oluştururken bu modeli kullandım, örneğin belirli bir tablo türü oluşturmak için bir Oluşturucuya sahip olabilirim ve aşağıdaki yöntemlere sahip olabilir (parametreler gösterilmiyor) :
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
Bu oluşturucu daha sonra benim için HTML tükürür. Bunu okumak, büyük bir prosedür yöntemiyle yürümekten çok daha kolaydır.
Wikipedia'da Oluşturucu Kalıbı'na göz atın .
Aşağıda, modelin ve örnek kodun Java'da kullanılmasının tartışıldığı bazı nedenler vardır, ancak Tasarım Desenlerinde Dörtlü Çete tarafından kapsanan Oluşturucu Deseni'nin bir uygulamasıdır . Java'da kullanmanızın nedenleri diğer programlama dilleri için de geçerlidir.
Joshua Bloch'un Etkili Java, 2. Baskıda belirttiği gibi :
Oluşturucu deseni, kurucuları veya statik fabrikaları birkaç parametreden daha fazlasına sahip olacak sınıflar tasarlarken iyi bir seçimdir.
Bir noktada, her eklemenin yeni bir seçenek parametresi eklediği kurucular listesiyle bir sınıfla karşılaştık:
Pizza(int size) { ... }
Pizza(int size, boolean cheese) { ... }
Pizza(int size, boolean cheese, boolean pepperoni) { ... }
Pizza(int size, boolean cheese, boolean pepperoni, boolean bacon) { ... }
Buna Teleskop Oluşturucu Deseni denir. Bu modeldeki sorun, kurucular 4 veya 5 parametre uzunluğunda olduğunda, parametrelerin gerekli sırasını ve belirli bir durumda hangi belirli kurucuyu isteyebileceğinizi hatırlamak zorlaşır .
Teleskop Oluşturucu Deseni'ne sahip olmanız gereken alternatiflerden biri , zorunlu parametreleri içeren bir kurucuyu çağırmanız ve sonra isteğe bağlı ayarlayıcıları çağırmanız gereken JavaBean Deseni'dir:
Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
Buradaki sorun, nesne birkaç çağrıda yaratıldığı için, yapısının yarıda tutarsız bir durumda olabilmesidir. Bu ayrıca iplik güvenliğini sağlamak için çok fazla çaba gerektirir.
Daha iyi bir alternatif, Oluşturucu Desenini kullanmaktır.
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;
}
}
Not Pizza değişmez ve bu parametre değerleri tek bir konumda hepsi . Builder'ın setter yöntemleri Builder nesnesini döndürdüğü için zincirlenebilirler .
Pizza pizza = new Pizza.Builder(12)
.cheese(true)
.pepperoni(true)
.bacon(true)
.build();
Bu, yazılması ve okunması ve anlaşılması çok kolay olan kodlarla sonuçlanır. Bu örnekte, derleme yöntemi , oluşturucudan Pizza nesnesine kopyalandıktan sonra parametreleri denetlemek ve geçersiz bir parametre değeri sağlandıysa bir IllegalStateException özel durumu atamak için parametreleri değiştirmek üzere değiştirilebilir. Bu desen esnektir ve gelecekte daha fazla parametre eklemek kolaydır. Gerçekten sadece bir kurucu için 4 veya 5'ten fazla parametreye sahip olacaksanız kullanışlıdır. Bununla birlikte, gelecekte daha fazla parametre ekleyeceğinizden şüpheleniyorsanız , ilk etapta faydalı olabilir.
Joshua Bloch'un 2.Baskı Etkili Java kitabından bu konudan büyük ödünç aldım . Bu model ve diğer etkili Java uygulamaları hakkında daha fazla bilgi edinmek için kesinlikle tavsiye ederim.
new Pizza.Builder(12).cheese().pepperoni().bacon().build();
Pizza.Builder(12).cheese().pepperoni().bacon().build();
kodunuzu yeniden derlemeniz veya yalnızca biraz pepperoni'ye ihtiyacınız varsa gereksiz mantığa sahip olmanız gerekir. pizzalar. En azından @Kamikaze Mercenary gibi önerilen parametreli versiyonları da sunmalısınız. Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();
. Sonra tekrar, asla birim test yapmıyoruz, değil mi?
Bir restoran düşünün. "Bugünün yemeği" nin oluşturulması bir fabrika modelidir, çünkü mutfağa "bugünkü yemeği al" deyin ve mutfak (fabrika) gizli kriterlere göre hangi nesnenin üretileceğine karar verirsiniz.
Özel bir pizza sipariş ederseniz oluşturucu görünür. Bu durumda, garson şefe (inşaatçı) "Pizzaya ihtiyacım var; peynir, soğan ve pastırma ekleyin!" Böylece, oluşturucu oluşturulan nesnenin sahip olması gereken öznitelikleri ortaya çıkarır, ancak bunların nasıl ayarlanacağını gizler.
.NET StringBuilder sınıfı, oluşturucu kalıbının harika bir örneğidir. Çoğunlukla bir dizi adımda bir dize oluşturmak için kullanılır. ToString () yöntemini kullanarak elde ettiğiniz son sonuç her zaman bir dizedir ancak bu dizenin oluşturulması StringBuilder sınıfında hangi işlevlerin kullanıldığına göre değişir. Özetle, temel fikir karmaşık nesneler oluşturmak ve nasıl inşa edildiğinin uygulama detaylarını gizlemektir.
b.append(...).append(...)
öğesinin kendisini nasıl döndürdüğüne dikkat edin, böylece son olarak aramadan önce zincir oluşturabilirsiniz toString()
. Alıntı: infoq.com/articles/internal-dsls-java
Çok iş parçacıklı bir sorun için, her iş parçacığı için karmaşık bir nesnenin oluşturulması gerekiyordu. Nesne, işlenmekte olan verileri temsil eder ve kullanıcı girişine bağlı olarak değişebilir.
Bunun yerine bir fabrika kullanabilir miyiz? Evet
Neden yapmadık? Builder sanırım daha mantıklı.
Fabrikalar, aynı temel tipte (aynı arabirimi veya temel sınıfı uygulayan) farklı nesne türleri oluşturmak için kullanılır.
İnşaatçılar aynı türden nesneyi tekrar tekrar inşa ederler, ancak yapı dinamiktir, böylece çalışma zamanında değiştirilebilir.
Başa çıkmak için birçok seçeneğiniz olduğunda kullanın. Jmock gibi şeyleri düşünün:
m.expects(once())
.method("testMethod")
.with(eq(1), eq(2))
.returns("someResponse");
Çok daha doğal bir his veriyor ve ... mümkün.
Ayrıca xml bina, dize bina ve daha birçok şey var. java.util.Map
Bir inşaatçı olarak koyduğunuzu düşünün . Bunun gibi şeyler yapabilirsiniz:
Map<String, Integer> m = new HashMap<String, Integer>()
.put("a", 1)
.put("b", 2)
.put("c", 3);
Microsoft MVC çerçevesinden geçerken, oluşturucu deseni hakkında bir düşünce aldım. ControllerBuilder sınıfındaki desenle karşılaştım. Bu sınıf, daha sonra beton kontrolörü oluşturmak için kullanılan kontrolör fabrika sınıfını döndürmektir.
Oluşturucu desenini kullanmanın avantajı, kendi fabrikanızı oluşturabilir ve çerçeveye bağlayabilirsiniz.
@Tetha, Pizza yapan İtalyan bir adam tarafından işletilen bir restoran (Çerçeve) olabilir. Pizza hazırlamak için İtalyan adam (Object Builder), pizza tabanı (temel sınıf) olan Owen'ı (Fabrika) kullanır.
Şimdi Hintli adam restoran İtalyan adam alır. Hint restoranı (Framework) sunucuları pizza yerine dosa. Dosa hazırlamak için Hintli adam (nesne oluşturucu) Maida (temel sınıf) ile Kızartma Tavası (Fabrika) kullanır
Senaryoya bakarsanız, yiyecek farklıdır, yemeğin hazırlanma şekli farklıdır, ancak aynı restoranda (aynı çerçeve altında). Restoran, Çin, Meksika veya herhangi bir mutfağı destekleyecek şekilde inşa edilmelidir. Çerçeve içindeki nesne oluşturucu, istediğiniz mutfağı eklenti yapmanızı kolaylaştırır. Örneğin
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;
}
}
Builder desenini her zaman hantal, rahatsız edici ve daha az deneyimli programcılar tarafından kötüye kullanılan bir şey olarak sevmedim. Yalnızca bir başlatma sonrası adım gerektiren bazı verilerden nesneyi birleştirmeniz gerektiğinde mantıklı olan bir modeldir (yani tüm veriler toplandıktan sonra - onunla bir şeyler yapın). Bunun yerine, zaman oluşturucuların% 99'unda sınıf üyeleri başlatmak için kullanılır.
Bu gibi durumlarda, sadece withXyz(...)
sınıf içindeki ayarlayıcıları bildirmek ve kendilerine bir referans döndürmelerini sağlamak daha iyidir .
Bunu düşün:
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");
Şimdi kendi başlatmayı yöneten ve çok daha zarif olması dışında, inşaatçı ile hemen hemen aynı işi yapan temiz bir tek sınıfımız var.
Oluşturucunun bir başka avantajı, bir Fabrikanız varsa, kodunuzda hala bazı bağlantıların bulunmasıdır, çünkü Fabrika'nın çalışması için, oluşturabileceği tüm nesneleri bilmelidir . Oluşturulabilecek başka bir nesne eklerseniz, onu dahil etmek için fabrika sınıfını değiştirmeniz gerekir. Bu, Soyut Fabrika'da da olur.
Inşaatçı ile Öte yandan, sadece bu yeni sınıf için yeni bir beton oluşturucu oluşturmak zorunda. Yönetmen sınıfı aynı kalacaktır, çünkü kurucuya yapıcıyı alır.
Ayrıca, inşaatçının birçok lezzeti vardır. Kamikaze Paralı Asker bir tane daha verir.
/// <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);
}
}
Önceki cevaplara dayanarak (pun amaçlı) mükemmel bir gerçek dünya örneği Groovy'nin yerleşik desteğidir Builders
.
MarkupBuilder
StreamingMarkupBuilder
SwingXBuilder
Bkz Üreticileri içinde Groovy Belgeler
Etkili Java'da açıklandığı gibi bir iç oluşturucu sınıfı oluşturan Oluştur menüsüne (Alt + Insert) bir 'Oluşturucu' eylemi ekleyen IntelliJ IDEA eklentisi InnerBuilder'e göz atın
Java'da DateTime'ın biçimlendirilmesini engellemek için XML'im için standart XMLGregorianCalendar kullanmak istediğimde, onu kullanmanın ne kadar ağır ve hantal olduğuna dair birçok yorum duydum. Ben xs: datetime yapıları saat dilimi, milisaniye, vb yönetmek için XML alanları kontrol etmeye çalışıyordu.
Bu yüzden bir GregorianCalendar veya java.util.Date'den XMLGregorian takvimi oluşturmak için bir yardımcı program tasarladım.
Nerede çalıştığımdan dolayı yasal olmadan çevrimiçi olarak paylaşmama izin verilmiyor, ancak bir müşterinin bunu nasıl kullandığına bir örnek. Ayrıntıları özetler ve xs: datetime için daha az kullanılan XMLGregorianCalendar uygulamasının bazı uygulamalarını filtreler.
XMLGregorianCalendarBuilder builder = XMLGregorianCalendarBuilder.newInstance(jdkDate);
XMLGregorianCalendar xmlCalendar = builder.excludeMillis().excludeOffset().build();
Bu kalıbın, xmlCalendar içindeki alanları tanımsız olarak ayarladığı için daha fazla filtre olduğu için hariç tutulurlar, yine de "oluşturur". Kolayca xs: date ve xs: time struct oluşturmak ve gerektiğinde saat dilimi ofsetlerini işlemek için oluşturucuya başka seçenekler ekledim.
XMLGregorianCalendar öğesini oluşturan ve kullanan bir kod gördüyseniz, bunun nasıl değiştirileceğini çok daha kolay hale getirirsiniz.
Gerçek bir dünya örneği, sınıflarınızı birim test ederken kullanmaktır. Sut (Test Altında Sistem) oluşturucularını kullanırsınız.
Misal:
Sınıf:
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;
}
Ölçek:
[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 Oluşturucu:
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
sınıfınıza ayarlayıcılar eklemek yerine bir kurucuya ihtiyacınız var ?