Oluşturucu Deseni: Ne zaman başarısız olunur?


45

Oluşturucu Örüntüsünü uygularken, çoğu zaman kendimi binanın arızalanmasına izin verdiğimde kafam karışık buluyorum ve hatta birkaç günde bir konu hakkında farklı tavırlar almayı başarabiliyorum.

İlk önce bazı açıklamalar:

  • İle erken başarısız Ben geçersiz bir parametre olarak geçirilir gibi bir nesneyi bina kısa sürede başarısız olması anlamına gelir. Yani içeride SomeObjectBuilder.
  • İle Geç başarısız Sadece bir nesne oluşturmaya başarısız anlamına build()metotunun nesnenin bir yapıcı inşa edilecek çağrıları çağrı.

Sonra bazı argümanlar:

  • Geç kalmamak için: Bir oluşturucu sınıf, sadece değerleri tutan bir sınıftan daha fazlası olmamalıdır. Dahası, daha az kod çoğaltmasına yol açar.
  • Erken başarısız olmanın lehine: Yazılım programlamaya genel bir yaklaşım, sorunları mümkün olduğunca erken tespit etmek istediğiniz ve bu nedenle kontrol edilmesi gereken en mantıklı yer, üretici sınıfında 'yapıcı,' ayarlayıcılar 've nihayetinde yapıcı yöntem olacaktır.

Bu konuda genel bir fikir birliği nedir?


8
Geç kalmamanın hiçbir avantajı görmüyorum. Birinin bir inşaat kurucusu sınıfının "olması gerektiği" demesi iyi tasarımdan öncelikli değildir ve böcekleri erken yakalamak, böcekleri geç yakalamaktan her zaman iyidir.
Doval,

3
Buna bakmanın başka bir yolu, inşaatçının neyin geçerli olduğunu bilmemesidir. Bu durumda erken başarısız olmak, bir hata olduğunu bildiğiniz anda başarısızlıkla sonuçlanır . Değil olması, erken dönen oluşturucu olur başarısız nullbir sorun içinde olduğunu nesneyi build().
Chris,

Bir uyarı vermenin ve teklif vermenin bir yolunu eklemezseniz, inşaatçıda düzeltmenin bir yolu yoktur; geç başarısız olmanızın bir anlamı yoktur.
Mark

Yanıtlar:


34

Doğrulama kodunu yerleştirebileceğimiz seçeneklere bakalım:

  1. Oluşturucudaki ayarlayıcıların içinde.
  2. İçinde build()yöntemle.
  3. Oluşturulan varlığın içinde: Varlığın build()yaratılması sırasında yöntem çağrılır .

Seçenek 1 , sorunları daha önce tespit etmemize izin verir, ancak girdiyi yalnızca tam içeriğe sahip olarak doğrulayabildiğimizde karmaşık durumlar olabilir, böylece build()yöntemde doğrulamanın en azından bir kısmını yaparsınız . Bu nedenle, seçenek 1'in seçilmesi, validasyonun bir yerinde yapılması ve bir başka bölümünün başka bir yerde yapılmasıyla tutarsız koda yol açacaktır.

Seçenek 2 , seçenek 1'den önemli ölçüde daha kötü değildir, çünkü genellikle oluşturucudaki ayarlayıcılar build(), özellikle de akıcı arayüzlerde hemen önce çağrılır . Bu nedenle, çoğu durumda bir sorunu yeterince erken tespit etmek hala mümkündür. Ancak, oluşturucu bir nesneyi yaratmanın tek yolu değilse, doğrulama kodunun çoğaltılmasına yol açar, çünkü nesneyi oluşturduğunuz her yerde bulundurmanız gerekir. Bu durumda en mantıklı çözüm, doğrulamayı, yaratılan nesneye mümkün olduğu kadar yakın hale getirmek olacaktır. Ve bu seçenek 3 .

SOLID bakış açısından, oluşturucuya doğrulama koymak aynı zamanda SRP'yi de ihlal eder: oluşturucu sınıfı zaten bir nesne oluşturmak için verileri toplama sorumluluğuna sahiptir. Doğrulama, kendi iç devletinde sözleşmeler yapmaktır, başka bir nesnenin durumunu kontrol etmek yeni bir sorumluluktur.

Bu nedenle, benim açımdan, sadece tasarım perspektifinden geç kalmamak daha iyi bir şey değil, aynı zamanda yapının kendisinde değil, inşa edilen varlık içinde başarısız olmak daha iyidir.

UPD: bu yorum , bir daha olasılıkla, hatırlattığında, oluşturucu içindeki doğrulama (seçenek 1 veya 2) anlamlıdır. Yapıcının yarattığı nesneler üzerinde kendi sözleşmelerinin olup olmadığı mantıklıdır. Örneğin, sayı aralıkları listesi gibi belirli bir içeriğe sahip bir dize oluşturan bir oluşturucumuz olduğunu varsayalım 1-2,3-4,5-6. Bu oluşturucu gibi bir yöntem olabilir addRange(int min, int max). Ortaya çıkan dize bu numaralar hakkında hiçbir şey bilmez, ne bilmesi gerekir de. Oluşturucu, dizgenin biçimini ve sayılardaki kısıtlamaları tanımlar. Bu nedenle, yöntem addRange(int,int)giriş numaralarını doğrulamalı ve eğer maksimum min. Değerden küçükse bir istisna atmalıdır.

Bununla birlikte, genel kural yalnızca inşaatçı tarafından tanımlanan sözleşmeleri doğrulamak olacaktır.


Onun 'için değer varken belirterek düşünüyorum Seçenek 1 "tutarsız" için kontrol sürelerini yol açabilir her şeydir, yine de tutarlı olarak görülebilir "mümkün olduğunca erken." Oluşturucu değişken, StepBuilder yerine kullanılıyorsa, "mümkün olduğunca erken" daha kesinleştirmek biraz daha kolay.
Joshua Taylor

Bir URI oluşturucusu, boş bir dize geçirilirse istisna atarsa, bu bir SOLID ihlali midir? Çöp
Gusdor

@ Gusdor evet, kendisi bir istisna atarsa. Bununla birlikte, kullanıcı bakış açısından, tüm seçenekler bir istisna yapana benziyor, üretici tarafından atılır.
Ivan Gammel

Öyleyse neden build () adında bir validate () yok? Bu şekilde çok az çoğaltma, tutarlılık ve SRP ihlali olmaz. Ayrıca, oluşturulmaya çalışmadan verileri doğrulamayı mümkün kılar ve doğrulama oluşturmaya yakındır.
StellarVortex

@StellarVortex bu durumda iki kez doğrulanır - bir kez builder.build () 'de, ve eğer veriler geçerliyse ve bu kurucudaki nesnenin kurucusuna ilerlersek.
Ivan Gammel

34

Java kullandığınız için, Java Nesneleri Yaratma ve Yok Etme başlıklı makalesinde Joshua Bloch tarafından sağlanan yetkili ve ayrıntılı rehberliği göz önünde bulundurun (aşağıdaki alıntıda kalın yazı tipi benimdir):

Bir inşaatçı gibi, inşaatçı değişmezleri parametrelerine uygulayabilir. Derleme yöntemi bu değişmezleri kontrol edebilir. Parametreleri yapıcıdan nesneye kopyaladıktan sonra kontrol edilmeleri ve yapıcı alanlardan ziyade nesne alanlarından kontrol edilmeleri çok önemlidir (Madde 39). Herhangi bir değişmez ihlal edilirse, derleme yöntemi bir tane atmalıdır IllegalStateException(Öğe 60). İstisnanın ayrıntılı yöntemi, hangi değişmezliğin ihlal edildiğini göstermelidir (Madde 63).

Birden fazla parametre içeren değişmezleri empoze etmenin bir başka yolu, belirleyici yöntemlerin bazı değişmezlerin üzerinde tutması gereken bütün parametre gruplarını almasıdır. Değişmez tatmin edici değilse, ayarlayıcı yöntem bir atar IllegalArgumentException. Bu, derleme çağrısı yapılmasını beklemek yerine, geçersiz parametreler geçilmez gerçekleşmez değişmeyen arızayı algılama avantajına sahiptir.

Bu yazıdaki editörün açıklamasına göre , yukarıdaki alıntıdaki "eşyalar" Etkili Java, İkinci Sürüm'de belirtilen kurallara atıfta bulunmaktadır .

Makale, bunun neden önerildiğini açıklamaya derinlemesine dalmıyor, ancak bunu düşünüyorsanız, nedenler oldukça açık. Bunu anlama konusundaki genel ipucu, tam burada makalede, oluşturucu kavramının kurucu ile olan ilişkisine nasıl bağlandığının açıklanmasında ve sınıf değişmezlerinin, başlatılmadan önce / hazırlanacak başka bir kodda değil, kurucuda kontrol edilmesi beklenmektedir.

Bir inşaa başlamadan önce değişmeyenleri kontrol etmenin neden daha somut olduğunu anlamak için popüler bir CarBuilder örneği düşünün . Oluşturucu yöntemleri isteğe bağlı bir sırada çağrılabilir ve sonuç olarak, belirli bir parametrenin derlemeye kadar geçerli olup olmadığı gerçekten bilemez.

Spor otomobilinin 2'den fazla koltuğa sahip olmadığını düşünün setSeats(4), tamam olup olmadığını nasıl bilebiliriz ? Sadece yapılıp setSportsCar()yapılmadığı , ne yapılıp atılmadığı kesin olarak bilindiği zaman, yapım aşamasındadır TooManySeatsException.


3
Hangi istisna türünün atılacağını önermek için + 1, tam olarak ne aradım.
Xantix

Alternatif aldığımdan emin değilim. Tamamen değişmeyenlerin yalnızca gruplar halinde doğrulanabildiği durumlarda konuşuyor gibi görünüyor. Oluşturucu, başkalarını içermediğinde tek öznitelikleri kabul eder ve yalnızca grup kendi üzerinde değişmez olduğunda öznitelik grupları kabul eder. Bu durumda, tek özellik derlemeden önce bir istisna atmalıdır mı?
Didier A.

19

Toleranslı olmadıkları için geçersiz olan geçersiz değerler bence derhal bildirilmelidir. Başka bir deyişle, yalnızca pozitif sayıları kabul ediyorsanız ve negatif bir sayı iletilirse build(), çağrılana kadar beklemeniz gerekmez . Bu gibi "beklediğiniz" türden problemleri göz önünde bulundurmam, çünkü bu yöntemle başlamak için bir önkoşuldur. Başka bir deyişle, bazı parametrelerin ayarlanmamasına bağlı kalmayacaksınız . Büyük olasılıkla parametrelerin doğru olduğunu varsayarsınız ya da kendiniz kontrol edersiniz.

Ancak, kolayca doğrulanamayan daha karmaşık konular için aradığınızda bilinmesi daha iyi olabilir build(). Buna iyi bir örnek, bir veritabanına bağlantı kurmak için sağladığınız bağlantı bilgilerini kullanıyor olabilir. Teknik olarak ise bu durumda, olabilir böyle durumları kontrol, artık sezgisel ve sadece kodunuzu zorlaştırmaktadır. Gördüğüm gibi, bunlar da gerçekten olabilecek ve deneyene kadar gerçekten bekleyemeyeceğiniz türlerdir. Bu tür bunun olmadığını görmek için normal bir ifade ile bir dize eşleşen arasındaki farkın var olabilir int olarak çözümlenen ve basit bir sonucu olarak ortaya çıkabilecek herhangi bir potansiyel durumları işleme, bunu ayrıştırmak için çalışıyor.

Parametreleri ayarlarken genellikle istisnalar atmaktan hoşlanmam çünkü bu durum, atılan herhangi bir istisnayı yakalamak zorunda kaldığından, onaylamayı tercih ediyorum build(). Bu nedenle, RuntimeException'ı kullanmayı tercih ediyorum, çünkü yine de parametrelerdeki hatalar genellikle gerçekleşmemeli.

Ancak, bu her şeyden daha iyi bir uygulama. Umarım bu sorunuza cevap verir.


11

Bildiğim kadarıyla, genel uygulama (fikir birliği olup olmadığından emin değilim), bir hatanın fizibil olarak tespit edilebildiği kadar erken başarısız olmasıdır. Bu aynı zamanda API'nızı istemeden kötüye kullanmaktan daha zor hale getirir.

Olumsuz olması gereken kapasite veya uzunluk gibi girdi üzerinde kontrol edilebilecek önemsiz bir özellik ise, derhal başarısız olmanız en iyisidir. Hatayı kaldırmak, hata ve geribildirim arasındaki mesafeyi arttırır ve bu da sorunun kaynağını bulmayı zorlaştırır.

Bir özniteliğin geçerliliğinin başkalarına bağlı olduğu bir durumda olma konusundaki talihsizlik varsa, iki seçeneğiniz vardır:

  • Her iki (veya daha fazla) özniteliğin aynı anda sağlanmasını (örneğin, tek yöntem çağrısı) gerektirir.
  • Başka hiçbir değişiklik gelmediğini biliyorsanız, geçerliliği test edin: ne zaman build()veya ne zaman çağrılır.

Çoğu şeyde olduğu gibi, bu bir bağlamda verilen bir karardır. Bağlam erken başarısız olmayı zorlaştırır veya karmaşık hale getirirse, çekleri daha sonra ertelemek için takas yapılabilir, ancak başarısız hızlı varsayılan olmalıdır.


Özetlemek gerekirse, bir nesne / ilkel tipte ele alınabilecek her şeyi mümkün olduğunca erken onaylamanın makul olduğunu söylüyorsunuz. Gibi unsigned,, @NonNullvb
skiwi

2
@skiwi Çok fazla, evet. Etki alanı denetimleri, boş denetimler, bu tür şeyler. Bundan daha fazlasını vermeyi savunmuyorum: inşaatçılar genellikle basit şeylerdir.
JvR

1
Bir parametrenin geçerliliğinin bir başkasının değerine bağlı olması durumunda, birinin diğerinin "gerçekten" kurulduğunu bilmesi durumunda, bir parametrenin değerini yalnızca meşru olarak reddedebileceğini belirtmekte fayda var . O [öncelik alarak geçen ayarıyla] bir parametre değeri birden çok kez ayarlamak için izin verilir, sonra bazı durumlarda bir nesneyi kurmak için en doğal yolu seti parametreye olabilir Xbugünkü değeri verilen geçersiz bir değere Y, ancak aramadan önce geçerli olacak bir değere build()ayarlayın . YX
supercat,

Örneğin, biri bir bina inşa ediyorsa Shapeve inşaatçının özellikleri WithLeftve WithRightözellikleri varsa ve biri bir nesneyi farklı bir yerde bir nesneyi inşa edecek şekilde ayarlamak istemişse, WithRightbir nesneyi sağa hareket ettirirken ilk olarak çağrılmasını gerektiren ve WithLeftonu sola hareket ettirirken, gereksiz karmaşıklık ekleyecektir. Sağ kenarı , çağrılmadan önce sağ kenarı sabitleyen WithLefteski sağ kenarın sağına ayarlamak için izin vermekle karşılaştırıldığında . WithRightbuild
supercat,

0

Temel kural "erken başarısız" dır.

Biraz daha gelişmiş kural "mümkün olduğunca erken başarısız" dır.

Bir özellik aslında geçersizse ...

CarBuilder.numberOfWheels( -1 ). ...  

... sonra hemen reddedersin.

Değerleri gerekebilir Diğer vakalar kontrol edilecek kombinasyon halinde ve daha iyi inşa () yönteminde yerleştirilmiş olabilir:

CarBuilder.numberOfWheels( 0 ).type( 'Hovercraft' ). ...  
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.