Bir nesneyi bir kurucuya aktarmalı mıyım yoksa sınıfta mı başlatmalıyım?


10

Bu iki örneği ele alalım:

Bir nesneyi kurucuya aktarma

class ExampleA
{
  private $config;
  public function __construct($config)
  {
     $this->config = $config;
  }
}
$config = new Config;
$exampleA = new ExampleA($config);

Sınıfı örnekleme

class ExampleB
{
  private $config;
  public function __construct()
  {
     $this->config = new Config;
  }
}
$exampleA = new ExampleA();

Bir nesneyi özellik olarak eklemeyi işlemenin doğru yolu hangisidir? Birini diğeri ne zaman kullanmalıyım? Birim testi neyi kullanmam gerektiğini etkiler mi?



9
@FrustratedWithFormsDesigner - Kod İnceleme için uygun değil.
ChrisF

Yanıtlar:


14

Sanırım birincisi size configbaşka bir yerde bir nesne yaratma ve onu aktarma yeteneği verecek ExampleA. Bağımlılık Enjeksiyonuna ihtiyacınız varsa, bu iyi bir şey olabilir, çünkü tüm örneklerin aynı nesneyi paylaşmasını sağlayabilirsiniz.

Öte yandan, belki de yeni ve temiz bir nesne ExampleA gerektirir , configböylece her örneğin farklı bir yapılandırmaya sahip olabileceği durumlar gibi ikinci örneğin daha uygun olduğu durumlar olabilir.


1
Yani, A birim testi için iyidir, B diğer durumlar için iyidir ... neden her ikisine de sahip değilsiniz? Sıklıkla biri manuel DI, biri normal kullanım için iki kurucum var (DI gereksinimlerini karşılamak için yapıcılar üretecek Unity for DI kullanıyorum).
Ed James

3
@EdWoodcock, diğer koşullara karşı birim testi ile ilgili değil. Nesne ya dışarıdan bağımlılık ister ya da içeriden yönetir. Asla ikisi de / ikisi de.
MattDavey

9

Test edilebilirliği unutma!

Genellikle Examplesınıfın davranışı , sınıf örneğinin iç durumunu kaydetmeden / değiştirmeden sınamak istediğiniz yapılandırmaya bağlıysa (yani özelliği değiştirmeden mutlu yol ve yanlış yapılandırma için basit testler yapmak istersiniz / Examplesınıf memeber ).

Bu yüzden ilk seçeneği ile gitmek istiyorum ExampleA


Bu yüzden testten bahsettim - bunu eklediğiniz için teşekkürler :)
Mahkum

5

Nesnenin bağımlılığın ömrünü yönetme sorumluluğu varsa, nesneyi yapıcıda oluşturmak (ve yıkıcıya atmak) sorun olmaz. *

Nesne bağımlılığın ömrünü yönetmekten sorumlu değilse, kurucuya aktarılmalı ve dışarıdan yönetilmelidir (örneğin bir IoC konteyneri tarafından).

Bu durumda, ClassA'nızın, atmaktan da sorumlu olmadığı sürece veya config her ClassA örneği için benzersiz değilse $ config oluşturma sorumluluğunu alması gerektiğini düşünmüyorum.

* Test edilebilirliğe yardımcı olmak için, yapıcı, yapıcısında bağımlılığı oluşturmak için bir fabrika sınıfına / yönteme atıfta bulunabilir, bu da uyumu ve test edilebilirliği arttırır.

//Object which manages the lifetime of its dependency (C#):
public class ClassA : IDisposable
{
    public Config Config { get; private set; }

    public ClassA()
    {
        this.Config = new Config(); // Tightly coupled to Config class...
    }

    public void Dispose()
    {
        this.Config.Dispose();
    }
}

// Object which does not manage its dependency:
public class ClassA
{
    public Config Config { get; set; }

    public ClassA(Config config) // dependency may be injected...
    {
        this.Config = config;
    }
}

// Object which manages its dependency in a testable way:
public class ClassA : IDisposable
{
    public Config Config { get; private set; }

    public ClassA(IConfigFactory configFactory) // dependency may be mocked...
    {
        this.Config = configFactory.BuildConfig();
    }

    public void Dispose()
    {
        this.Config.Dispose();
    }
}

2

Son zamanlarda mimarlık ekibimizle aynı tartışmayı yaptım ve bunu şu ya da bu şekilde yapmak için bazı ince nedenler var. Çoğunlukla Bağımlılık Enjeksiyonu (diğerlerinin belirttiği gibi) ve yapıcıda yeni oluşturduğunuz nesnenin yaratılması üzerinde gerçekten kontrolünüz olup olmadığına gelir.

Örneğinizde, ya Config sınıfınız:

a) havuzdan veya fabrika yönteminden gelenler gibi önemsiz olmayan tahsislere sahiptir.

b) tahsis edilemedi. Yapıcıya aktarmak düzgünce bu sorunu önler.

c) aslında Config'in bir alt sınıfıdır.

Nesneyi kurucuya geçirmek en fazla esnekliği sağlar.


1

Aşağıdaki cevap yanlış, ancak başkalarının ondan öğrenmesini sağlayacağım (aşağıya bakın)

İçinde ExampleA, aynı Configörneği birden çok sınıfta kullanabilirsiniz. Bununla birlikte, Configtüm uygulama içinde yalnızca bir örnek olması gerekiyorsa Config, birden çok örneğine sahip olmaktan kaçınmak için Singleton desenini uygulamayı düşünün Config. Ve eğer ConfigSingleton ise , bunun yerine aşağıdakileri yapabilirsiniz:

class ExampleA
{
  private $config;
  public function __construct()
  {
     $this->config = Config->getInstance();
  }
}
$exampleA = new ExampleA();

Öte ExampleByandan, Configher örneği için her zaman ayrı bir örneği alırsınız ExampleB.

Hangi sürümü uygulamanız gerektiği, uygulamanın şu örnekleri nasıl ele alacağına bağlıdır Config:

  • Her örneği ise ExampleXayrı bir örneğini olmalıdır Config, ile gitmek ExampleB;
  • her bir örneği, eğer ExampleXbir (ve yalnızca bir) örneğini paylaşacak Config, kullanıcı ExampleA with Config Singleton;
  • örnekleri ExampleXfarklı örnekleri kullanıyorsa Config, ile sopa ExampleA.

Neden dönüştürme Configbir içine Singleton yanlıştır:

Sadece Singleton desenini dün öğrendiğimi itiraf etmeliyim ( ilk tasarım desenleri kitabını okuyarak ). Saf bir şekilde bu örnek için devam ettim ve uyguladım, ancak birçok kişinin belirttiği gibi, bir yol başka bir şey (bazıları daha şifreli ve sadece "Yanlış yapıyorsun!" Dedi), bu iyi bir fikir değil. Yani, başkalarının benim yaptığımla aynı hatayı yapmalarını önlemek için, burada Singleton deseninin neden zararlı olabileceğinin bir özeti var ( yorumlara ve Google'da bulduğum şeylere dayanarak):

  1. Örneğe ExampleAkendi başvurusunu alırsa Config, sınıflar sıkıca bağlanır. ExampleAFarklı bir sürümünü kullanmak için bir örneğe sahip olmanın bir yolu olmayacaktır Config(bazı alt sınıfları söyleyin). Bunu sağlamak için bir yolu olmadığı için ExampleAbir mock-up örneği kullanarak test etmek istiyorsanız bu korkunç .ConfigExampleA

  2. Oradan öncül örneği biriniz, sadece biri olacak Configbelki tutar şimdi , ama her zaman emin olamaz aynı irade tutma gelecekte . Daha sonraki bir noktada, birden çok örneğinin Configisteneceği ortaya çıkarsa , kodu yeniden yazmadan bunu başarmanın bir yolu yoktur.

  3. Bir-bir-bir-örneği Configtüm sonsuzluk için doğru olsa bile, Config(sadece tek bir örneği olsa da) bazı alt sınıflarını kullanmak isteyebilirsiniz . Kod doğrudan aracılığıyla örneğini alır çünkü Ama, getInstance()bir Configbir olan staticyöntem, alt sınıf almak için bir yolu yoktur. Yine, kod yeniden yazılmalıdır.

  4. En azından sadece API'sini görüntülerken, ExampleAkullanımları Configgizlenecektir ExampleA. Bu kötü bir şey olabilir veya olmayabilir, ama kişisel olarak bunun bir dezavantaj gibi olduğunu hissediyorum; örneğin, devam ederken, Configdiğer sınıfların uygulanmasına bakmadan hangi sınıfların değişikliklerden etkileneceğini bulmanın basit bir yolu yoktur .

  5. Aslında bile ExampleAbir kullanır Singleton Config başlı başına bir sorun değildir, hala bir görüş test noktasından bir sorun haline gelebilir. Singleton nesneleri, uygulamanın sonlandırılmasına kadar devam edecek bir durum taşır. Bu, birim testlerini çalıştırırken bir testin diğerinden izole edilmesini istediğiniz için bir sorun olabilir (yani bir testin gerçekleştirilmesi diğerinin sonucunu etkilememelidir). Bunu düzeltmek için, Singleton nesnesi her test çalıştırması arasında imha edilmelidir (potansiyel olarak tüm uygulamayı yeniden başlatmanız gerekir), bu da zaman alıcı olabilir (sıkıcı ve can sıkıcı bir durumdan bahsetmiyorum).

Bunu söyledikten, ben böyle bir hata sevindim burada gerçek bir uygulamanın uygulanmasında değil. Aslında, bazı sınıflar için Singleton modelini kullanmak için en son kodumu yeniden yazmayı düşünüyordum . Değişiklikleri kolayca geri alabilsem de (elbette her şey bir SVN'de saklanır), yine de bunu yapmak için zaman harcamıştım.


4
Ben bunu tavsiye etmem .. Bu şekilde sıkıca sınıf çift ExampleAve Config- ki iyi bir şey değil.
Paul

@ Paul: Bu doğru. İyi yakaladın, bunu düşünmedim.
gablin

3
Her zaman test edilebilecek nedenlerden dolayı Singletons kullanılmamasını öneriyorum. Temelde küresel değişkenlerdir ve bağımlılıkla alay etmek imkansızdır.
MattDavey

4
Yanlış yaptığınız için Singletons kullanarak tekrar tekrar tavsiye ederim.
Raynos

1

Yapılacak en basit şey çift ExampleAolmaktır Config. Daha karmaşık bir şey yapmak için zorlayıcı bir neden yoksa, en basit şeyi yapmalısınız.

Ayrılma için bir sebep ExampleAve Configtest edilebilirliğini artırmak olacaktır ExampleA. Doğrudan birleştirme bölgesinin test edilebilir düşer ExampleAise Config, yavaş, karmaşık ya da hızla gelişen olan yöntemleri vardır. Test için, bir yöntem birkaç mikrosaniyeden fazla çalışıyorsa yavaştır. Tüm yöntemler ise Configbasit ve hızlıdır, o zaman basit bir yaklaşım ve doğrudan çift alacağını ExampleAetmek Config.


1

İlk örneğiniz Bağımlılık Enjeksiyonu kalıbına bir örnektir. Harici bağımlılığa sahip bir sınıfa bağımlılık yapıcı, ayarlayıcı vb. Tarafından verilir.

Bu yaklaşım gevşek bağlanmış kodla sonuçlanır. Çoğu insan gevşek bağlantının iyi bir şey olduğunu düşünür, çünkü bir nesnenin belirli bir örneğinin diğerlerinden farklı olarak yapılandırılması gerektiği durumlarda yapılandırmayı kolayca değiştirebilirsiniz, böylece test için bir sahte yapılandırma nesnesine geçebilirsiniz. üzerinde.

İkinci yaklaşım GRASP yaratıcı modeline daha yakındır. Bu durumda, nesne kendi bağımlılıklarını oluşturur. Bu, sıkı bir şekilde birleştirilmiş kod ile sonuçlanır, bu sınıfın esnekliğini sınırlayabilir ve test etmeyi zorlaştırabilir. Diğerlerinden farklı bir bağımlılığa sahip olmak için sınıfın bir örneğine ihtiyacınız varsa, tek seçeneğiniz onu alt sınıfa ayırmaktır.

Bununla birlikte, bağımlı nesnenin ömrünün bağımlı nesnenin ömrü tarafından belirlendiği ve bağımlı nesnenin kendisine bağlı nesnenin dışında hiçbir yerde kullanılmadığı durumlarda uygun desen olabilir. Normalde DI'ye varsayılan konum olmasını tavsiye ederim, ancak sonuçlarının farkında olduğunuz sürece diğer yaklaşımı tamamen dışlamak zorunda değilsiniz.


0

Sınıfınız $configdış sınıflara maruz kalmazsa , onu yapıcı içinde oluştururdum. Bu şekilde, dahili durumunuzu gizli tutarsınız.

Eğer $configdüzgün ayarlanması kendi iç durumunu gerektirir gerektirir o zaman bazı harici kod (muhtemelen bir fabrika sınıfı) için başlatma erteleme mantıklı ve bunun enjekte, kullanımdan önce (örneğin başlatıldı bir veritabanı bağlantısı veya bazı iç alanları ihtiyacı) yapıcı. Ya da, başkalarının belirttiği gibi, diğer nesneler arasında paylaşılması gerekiyorsa.


0

Örnek A, Config türünden değil, Config'in soyut üst sınıfının türünden alındığında sağlanan nesne sağlanan , iyi olan Config sınıfı beton sınıfından ayrılmıştır .

ÖrnekB, kötü olan beton sınıfı Config ile güçlü bir şekilde bağlantılıdır .

Bir nesneyi örneklemek sınıflar arasında güçlü bir bağlantı oluşturur. Fabrika sınıfında yapılmalıdır.

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.