Bağımlılık Enjeksiyonu veya statik fabrikalar mı kullanmalıyım?


81

Bir sistemi tasarlarken, sıklıkla diğer modüller tarafından kullanılan bir grup modülün (kayıt, veritabanı erişimi, vb.) Olması problemiyle karşılaşıyorum. Asıl soru, bu bileşenleri diğer bileşenlere nasıl sunacağım. İki cevap olası bağımlılık enjeksiyonunu veya fabrika şablonunu kullanarak görünmektedir. Ancak her ikisi de yanlış görünüyor:

  • Fabrikalar testi zorlaştırıyor ve uygulamaların kolayca değiştirilebilmesine izin vermiyor. Ayrıca bağımlılıkları belirginleştirmezler (örneğin, bir metodu inceliyorsunuz, bir veritabanını kullanan bir metodu çağıran bir metodu çağırdığı gerçeğinden habersiz).
  • Bağımlılık enjeksiyonu, yapıcı argüman listelerini toplu olarak şişirir ve tüm kodunuzu kodlar. Tipik durum, yarıdan fazla sınıfın kurucularının böyle görünmesidir.(....., LoggingProvider l, DbSessionProvider db, ExceptionFactory d, UserSession sess, Descriptions d)

İşte tipik bir durum var: Sorunum var: Veritabanından yüklenen hata açıklamalarını kullanan ve kullanıcı oturumu nesnesindeki kullanıcı dili ayar parametresi olan bir sorgu kullanarak istisna sınıfları var. Yeni bir İstisna oluşturmak için bir veritabanı oturumu ve kullanıcı oturumu gerektiren bir açıklamaya ihtiyacım var. Bu yüzden, bir istisna atmam gerekebilir diye, tüm bu nesneleri tüm yöntemlerim boyunca sürüklemeye mahkumum.

Böyle bir problemi nasıl çözebilirim?


2
Eğer bir fabrika tüm problemlerinizi çözebiliyorsa, belki de sadece fabrikayı nesnelerinize enjekte edebilir ve bundan LoggingProvider, DbSessionProvider, ExceptionFactory, UserSession alabilirsiniz.
Giorgio

1
Bir yönteme çok fazla "Girdi", iletilmeleri veya enjekte edilmeleri, yöntem tasarımının kendisinde daha büyük bir problemdir. Hangisiyle giderseniz gidin, yönteminizin boyutunu biraz azaltmak isteyebilirsiniz (enjeksiyon yapıldığında bir kez daha kolaylaşır)
Bill K

Buradaki çözüm argümanları azaltmamalı. Bunun yerine, nesnedeki tüm işi yapan ve size fayda sağlayan daha yüksek düzeyde bir nesne oluşturan soyutlamalar oluşturun.
Alex,

Yanıtlar:


74

Bağımlılık enjeksiyonunu kullanın, ancak yapıcı argüman listeleriniz çok büyük olduğunda, Cephe Hizmeti kullanarak yeniden uygulayın . Buradaki fikir, yapıcı argümanlarından bazılarını birlikte gruplamak ve yeni bir soyutlama sunmaktır.

Örneğin, SessionEnvironmenta DBSessionProvider, the UserSessionve the load'ü içine alan yeni bir tür sunabilirsiniz Descriptions. Hangi soyutlamaların en anlamlı olduğunu bilmek için programınızın ayrıntılarını bilmek gerekir.

Benzer bir soru burada SO'da zaten sorulmuştu .


9
+1: Yapıcı argümanlarını sınıflara göre gruplandırmanın çok iyi bir fikir olduğunu düşünüyorum. Ayrıca, bu argümanı daha anlamlı yapılarda düzenlemeye zorlar.
Giorgio

5
Fakat sonuç anlamlı bir yapı değilse, o zaman sadece SRP'yi ihlal eden karmaşıklığı gizlemektesiniz. Bu durumda bir sınıf yeniden düzenlemesi yapılmalıdır.
danidacar

1
@Giorgio, "kurucu argümanlarını sınıflara ayırmanın çok iyi bir fikir olduğunu" belirten genel bir ifadeye katılmıyorum. Bunu "bu senaryoda" olarak nitelendirirseniz, o zaman farklıdır.
tymtam

19

Bağımlılık enjeksiyonu, yapıcı argüman listelerini toplu olarak şişirir ve tüm kodunuzu kodlar.

Bundan, DI'yi doğru bir şekilde anlıyor gibisiniz - fikir, bir fabrika içindeki nesne örnekleme modelini tersine çevirmektir.

Özel probleminiz daha genel bir OOP problemi gibi görünüyor. Neden nesneler çalışma süreleri boyunca normal, insan tarafından okunamayan istisnalar alamazlar ve sonra bu istisnayı yakalayan son deneme / yakalamadan önce bir şey alamazlar ve bu noktada oturum bilgilerini yeni, daha güzel bir istisna atmak için kullanır ?

Başka bir yaklaşım, kurucuları aracılığıyla nesnelere iletilen bir istisna fabrikasına sahip olmak olacaktır. Yeni bir istisna atmak yerine, sınıf fabrikanın bir metoduna atabilir (örn throw PrettyExceptionFactory.createException(data).

Nesnelerin, fabrika nesnelerinizin dışında, asla newoperatörü kullanmaması gerektiğini unutmayın . İstisnalar genellikle özel bir durumdur, ancak sizin durumunuzda bir istisna olabilir!


1
Bir yerde, parametre listeniz çok uzun olduğunda, bağımlılık enjeksiyonunu kullandığınızdan değil, daha fazla bağımlılık enjeksiyonuna ihtiyaç duyduğunuzdan dolayı olduğunu okudum.
Giorgio

Bu sebeplerden biri olabilir - genellikle dile bağlı olarak, kurucunuz 6-8 argümandan daha fazlasına sahip olmamalı ve belirli bir kalıp (örüntü gibi Builder) olmadıkça, bunların 3-4'ünden fazlası nesnelerin kendisi olmamalıdır. onu belirler. Nesneniz diğer nesneleri başlattığı için parametrelerinizi kurucunuza aktarıyorsanız, bu IoC için açık bir durumdur.
Jonathan Rich

12

Statik fabrika modelinin dezavantajlarını oldukça iyi listelediniz, ancak bağımlılık enjeksiyon modelinin dezavantajları ile tam olarak aynı fikirde değilim:

Bu bağımlılık enjeksiyonu, her bağımlılık için kod yazmanızı gerektirir, bir hata değildir, ancak bir özelliktir: Sizi bu bağımlılıklara gerçekten ihtiyaç duyup duymadığınızı düşünerek zorla bağlamayı teşvik eder. Örnekte:

İşte tipik bir durum var: Sorunum var: Veritabanından yüklenen hata açıklamalarını kullanan ve kullanıcı oturumu nesnesindeki kullanıcı dili ayar parametresi olan bir sorgu kullanarak istisna sınıfları var. Yeni bir İstisna oluşturmak için bir veritabanı oturumu ve kullanıcı oturumu gerektiren bir açıklamaya ihtiyacım var. Bu yüzden, bir istisna atmam gerekebilir diye, tüm bu nesneleri tüm yöntemlerim boyunca sürüklemeye mahkumum.

Hayır, mahkum değilsin. Belirli bir kullanıcı oturumu için hata iletilerinizi yerelleştirmek neden iş mantığının sorumluluğundadır? Gelecekte bir zamanlar bu işletme hizmetini bir toplu programdan (kullanıcı oturumu olmayan ...) kullanmak istersen? Ya da şu anda oturum açmış olan kullanıcıya hata mesajı gösterilmemesi için değil, süpervizörü (kim farklı bir dili tercih edebilir) gösterilmeli? Ya da istemcideki iş mantığını tekrar kullanmak istiyorsanız (bir veritabanına erişimi olmayan ...)?

Açıkçası, mesajların yerelleştirilmesi bu mesajlara kimin baktığına, yani sunum katmanının sorumluluğuna bağlıdır. Bu nedenle, iş hizmetinden olağan istisnalar atardım; bu, kullandığı mesaj kaynağında ne olursa olsun, sunum katmanının istisna işleyicisine bakılabilecek bir mesaj tanımlayıcısı taşır.

Bu şekilde, 3 gereksiz bağımlılığı kaldırabilirsiniz (UserSession, ExceptionFactory ve muhtemelen açıklamaları), böylece kodunuzu hem daha basit hem de çok yönlü hale getirebilirsiniz.

Genel olarak konuşursak, sadece her yerde erişmeniz gereken ve kodu çalıştırmak isteyebileceğimiz tüm ortamlarda (Logging gibi) bulunabileceği garantili statik fabrikalar kullanırdım. Her şey için eski düz bağımlılık enjeksiyonunu kullanırdım.


Bu. Bir istisna atmak için DB'ye vurma gereğini göremiyorum .
Caleth,

1

Bağımlılık enjeksiyonu kullanın. Statik fabrikaları kullanmak Service Locatorantipattern'in bir işidir. Martin Fowler'ın seminal çalışmasını burada görebilirsiniz - http://martinfowler.com/articles/injection.html

Yapıcı argümanlarınız çok büyükse ve bir DI konteyneri kullanmıyorsanız, kendi fabrikalarınızı örnekleme için yazarak yapılandırın, XML ile veya bir uygulamayı bir arabirime bağlayarak yapılandırın.


5
Hizmet Bulucu bir antipattern değil - Fowler, gönderdiğiniz URL’de kendisine referans veriyor. Servis Belirleyici modeli kötüye kullanılabilse de (Singletons'un kötüye kullanılmasıyla aynı şekilde - küresel durumu soyutlamak için), çok yararlı bir düzendir.
Jonathan Rich

1
Bilmek ilginç. Ben her zaman anti patern olarak adlandırıldığını duydum.
Sam

4
Servis bulucu global durumu depolamak için kullanılıyorsa, sadece bir antipattern. Servis bulucu, başlatıldıktan sonra durumsuz bir nesne olmalı ve tercihen değişmez olmalıdır.
Jonathan Rich

XML güvenli değil. Diğer tüm yaklaşımlar başarısız olduktan sonra bunu bir z planı olarak düşünürdüm
Richard Tingle

1

Bağımlılık Enjeksiyonu ile de iyi giderdim. DI'nin sadece inşaatçılar aracılığıyla değil, aynı zamanda Emlakçılar tarafından da yapıldığını unutmayın. Örneğin, kayıt cihazı bir özellik olarak enjekte edilebilir.

Ayrıca, örneğin, yapıcı parametrelerini çalışma zamanında ihtiyaç duyulan alanlara etki alanı mantığınız tarafından ihtiyaç duyulan şeylere (örneğin yapıcıyı amacını verecek şekilde tutarak) sizin için yükün bir kısmını kaldırabilecek bir IoC kabı kullanmak isteyebilirsiniz. sınıf ve gerçek etki alanı bağımlılıkları) ve belki diğer yardımcı sınıfları özellikler yoluyla enjekte edebilir.

Daha ileri gitmek isteyebileceğiniz bir adım, birçok büyük çerçevede uygulanan Yönelimli Program. Bu, sınıfın yapıcısını kesmenize (ya da AspectJ terminolojisini kullanmanız için "tavsiye vermenize") ve özel bir özellik verildiğinde ilgili özellikleri eklemenize izin verebilir.


4
DI ile ayarlayıcılardan kaçınıyorum, çünkü nesnenin tamamen başlatılmadığı (yapıcı ve ayarlayıcı çağrısı arasında) olmadığı bir zaman penceresi getiriyor. Başka bir deyişle, eğer mümkünse kaçındığım bir yöntem çağrısı emri verir (Y'den önce X'i çağırması gerekir).
RokL

1
DI yoluyla özellik ayarlayıcıları isteğe bağlı bağımlılıklar için idealdir. Günlük kaydı iyi bir örnektir. Günlüğe kaydetmeye ihtiyacınız varsa, daha sonra kaydetmediyseniz, Logger özelliğini ayarlayın.
Preston

1

Fabrikalar testi zorlaştırıyor ve uygulamaların kolayca değiştirilebilmesine izin vermiyor. Ayrıca bağımlılıkları belirginleştirmezler (örneğin, bir metodu inceliyorsunuz, bir veritabanını kullanan bir metodu çağıran bir metodu çağırdığı gerçeğinden habersiz).

Tam olarak aynı fikirde değilim. En azından genel olarak değil.

Basit fabrika:

public IFoo GetIFoo()
{
    return new Foo();
}

Basit enjeksiyon:

myDependencyInjector.Bind<IFoo>().To<Foo>();

Her iki snippet de aynı amaca hizmet eder, IFoove arasında bir bağlantı kurarlar Foo. Her şey sadece sözdizimidir.

Değişen Fooiçin ADifferentFooher iki kod örneğinde tam olarak çok çaba gerektirir.
İnsanların bağımlılık enjeksiyonunun farklı ciltlemelerin kullanılmasına izin verdiğini savunduğunu duydum, ancak aynı fabrikalar için de aynı tartışma yapılabilir. Doğru bağlayıcıyı seçmek, doğru fabrikayı seçmek kadar karmaşıktır.

Fabrika yöntemleri, örneğin Foobazı yerlerde ve ADifferentFoodiğer yerlerde kullanımınıza izin verir . Bazıları buna iyi diyebilir (ihtiyaç duyduğunuzda faydalı olabilir), bazıları buna kötü diyebilir (her şeyi değiştirirken yarı değerli bir iş yapabilirsiniz).
Ancak, IFooher zaman tek bir kaynağa sahip olmanız için geri dönen tek bir yönteme sadık kalırsanız, bu belirsizlikten kaçınmak zor değildir . Kendinizi ayağınızdan vurmak istemiyorsanız ya dolu bir silah tutmayın ya da ayağınıza doğrultmadığınızdan emin olun.


Bağımlılık enjeksiyonu, yapıcı argüman listelerini toplu olarak şişirir ve tüm kodunuzu kodlar.

Bu nedenle bazı insanlar yapıcıdaki bağımlılıkları açıkça almayı tercih ediyorlar, bunun gibi:

public MyService()
{
    _myFoo = DependencyFramework.Get<IFoo>();
}

Ben argümanlar pro duydum (yapıcı şişkinlik yok), argümanlar con duydum (yapıcı kullanarak daha fazla DI otomasyonunu sağlar).

Şahsen, ben yapıcı argümanlarını kullanmak isteyen üst düzeyimize bilgi verirken, VS'deki açılan listeyle ilgili bir sorun fark ettim (sağ üst, mevcut sınıfın yöntemlerine göz atmak için yöntem isimlerinin gözden kaçması durumunda) Yöntemin imzaları ekranımdan daha uzun (=> şişirilmiş kurucu).

Teknik düzeyde, hiçbir şekilde umrumda değil. Her iki seçenek de yazmak kadar çaba gerektirir. Ve DI kullandığınız için, genellikle yine de bir kurucu çağırmazsınız. Ancak Visual Studio UI hatası beni yapıcı argümanını şişirmemeye teşvik ediyor.


Bir yandan not olarak, bağımlılık enjeksiyonu ve fabrikalar birbirini dışlamaz . Bağımlılık eklemek yerine, bağımlılık üreten bir fabrika kurdum (NInject neyse ki kullanmanıza izin veriyor, Func<IFoo>böylece gerçek bir fabrika sınıfı oluşturmanıza gerek kalmaz).

Bunun için kullanım durumları nadirdir, ancak var.


OP statik fabrikaları soruyor . stackoverflow.com/questions/929021/…
Basilevs

@Basilevs "Soru şu, bu bileşenleri diğer bileşenlere nasıl sunacağım?"
Anthony Rutledge,

1
@Basilevs Yan not dışında söylediğim her şey statik fabrikalar için de geçerli. Özellikle neye işaret etmeye çalıştığınızdan emin değilim. Referanstaki bağlantı nedir?
Flater,

Bir fabrikanın enjekte edilmesinin bu tür bir kullanım durumu, birinin soyut bir HTTP istek sınıfını tasarladığı ve biri GET, POST, PUT, PATCH ve DELETE için olan beş diğer polimorfik çocuk sınıfını strateji yaptığı bir durum olabilir mi? Sen bir HTTP isteği sınıfında kısmen dayanabilir bir MVC tip yönlendirici (ile adı geçen sınıf hiyerarşisi bütünleştirmek için çalışıyoruz her zaman kullanılacak olan HTTP yöntemi bilemez PSR-7 HTTP Mesaj Arayüz çirkin..
Anthony Rutledge

@AnthonyRutledge: DI fabrikaları iki anlama gelebilir (1) Birden çok yöntemi ortaya çıkaran bir sınıf. Bence bunun hakkında konuşuyorsun. Ancak, bu fabrikalara gerçekten özel değil. Birkaç kamu yöntemiyle bir işletme mantığı sınıfı ya da birkaç kamu yöntemiyle bir fabrika olması anlambilim meselesidir ve teknik bir fark yaratmaz. (2) Fabrikalar için DI'ye özel kullanım örneği, fabrika dışı bir bağımlılığın bir kez (enjeksiyon sırasında) başlatıldığı, fabrika bağımlılığının gerçek bağımlılığı daha sonraki bir aşamada (ve muhtemelen birden fazla defa) başlatmak için kullanılabileceğidir.
flater

0

Bu sahte örnekte, bir fabrika sınıfı çalışma zamanında HTTP istek yöntemine dayanarak hangi tür gelen HTTP istek nesnesinin somutlaştırılacağını belirlemek için kullanılır. Fabrikanın kendisine bir bağımlılık enjeksiyon kabı örneği enjekte edilir. Bu, fabrikanın çalışma zamanını belirlemesini ve bağımlılık enjeksiyon kabının bağımlılıkları idare etmesini sağlar. Her gelen HTTP istek nesnesinin en az dört bağımlılığı vardır (süper küreseller ve diğer nesneler).

<?php
namespace TFWD\Factories;

/**
 * A class responsible for instantiating
 * InboundHttpRequest objects (PHP 7.x)
 * 
 * @author Anthony E. Rutledge
 * @version 2.0
 */
class InboundHttpRequestFactory 
{
    private const GET = 'GET';
    private const POST = 'POST';
    private const PUT = 'PUT';
    private const PATCH = 'PATCH';
    private const DELETE = 'DELETE';

    private static $di;
    private static $method;

    // public function __construct(Injector $di, Validator $httpRequestValidator)
    // {
    //    $this->di = $di;
    //    $this->method = $httpRequestValidator->getMethod();
    // }

    public static function setInjector(Injector $di)
    {
        self::$di = $di;
    }    

    public static setMethod(string $method)
    {
        self::$method = $method;
    }

    public static function getRequest()
    {
        if (self::$method == self::GET) {
            return self::$di->get('InboundGetHttpRequest');
        } elseif ((self::$method == self::POST) && empty($_FILES)) {
            return self::$di->get('InboundPostHttpRequest');
        } elseif (self::$method == self::POST) {
            return self::$di->get('InboundFilePostHttpRequest');
        } elseif (self::$method == self::PUT) {
            return self::$di->get('InboundPutHttpRequest');
        } elseif (self::$method == self::PATCH) {
            return self::$di->get('InboundPatchHttpRequest');
        } elseif (self::$method == self::DELETE) {
            return self::$di->get('InboundDeleteHttpRequest');
        } else {
            throw new \RuntimeException("Unexpected HTTP request. Invalid request.");
        }
    }
}

Merkezileştirilmiş bir MVC tipi kurulum için müşteri kodu index.php, aşağıdakine benzeyebilir (doğrulama yapılmamıştır).

InboundHttpRequestFactory::setInjector($di);
InboundHttpRequestFactory::setMethod($httpRequestValidator->getMethod());
$di->set('InboundHttpRequest', InboundHttpRequestFactory::getRequest());
$router = $di->get('Router');  // The Router class depends on InboundHttpRequest objects.
$router->dispatch(); 

Alternatif olarak, fabrikanın statik yapısını (ve anahtar kelimesini) kaldırabilir ve bir bağımlılık enjektörünün her şeyi (dolayısıyla yorum yapan kurucu) yönetmesine izin verebilirsiniz. Ancak, sınıf üyesi referanslarının ( self) bazılarını (sabitleri değil ) örnek üyelere ( $this) değiştirmeniz gerekecektir .


Yorum içermeyen indirimler yararlı değildir.
Anthony Rutledge
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.