ServiceLocator bir anti-desen midir?


137

Son zamanlarda Mark Seemann'ın Service Locator anti-pattern hakkındaki makalesini okudum .

Yazar, ServiceLocator'ın bir anti-desen olmasının iki ana nedenine dikkat çekiyor:

  1. API kullanım sorunu (ki ben çok iyiyim)
    Sınıf bir Servis bulucu kullandığında, çoğu durumda sınıfın yalnızca bir PARAMETERLESS yapıcısı olduğu için bağımlılıklarını görmek çok zordur. ServiceLocator'ın aksine DI yaklaşımı, bağımlılıkları yapıcı parametreleri aracılığıyla açıkça ortaya koyar, böylece IntelliSense'te bağımlılıklar kolayca görülebilir.

  2. Bakım sorunu (beni
    şaşırtan ) Aşağıdaki örneği düşünün

Bir Servis bulucu yaklaşımı kullanan bir 'MyType' sınıfımız var :

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();
    }
}

Şimdi 'MyType' sınıfına başka bir bağımlılık eklemek istiyoruz

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

Ve burada yanlış anlaşılmam başlıyor. Yazar diyor ki:

Kırılma değişikliği yapıp yapmadığınızı söylemek çok daha zorlaşıyor. Servis Bulucu'nun kullanıldığı tüm uygulamayı anlamalısınız ve derleyici size yardımcı olmayacaktır.

Ama bir saniye bekleyin, eğer DI yaklaşımını kullanıyor olsaydık, yapıcıdaki başka bir parametreye (yapıcı enjeksiyonu durumunda) bir bağımlılık getirirdik. Ve sorun hala orada olacak. ServiceLocator kurmayı unutabilirsek, IoC kapsayıcımıza yeni bir eşleme eklemeyi unutabiliriz ve DI yaklaşımı aynı çalışma zamanı sorununa sahip olur.

Ayrıca, yazar birim test zorluklarından bahsetti. Ancak, DI yaklaşımıyla ilgili sorunlarımız olmayacak mı? Bu sınıfı başlatan tüm testleri güncellememiz gerekmeyecek mi? Testimizi derlenebilir hale getirmek için onları yeni bir alay bağımlılığını geçecek şekilde güncelleyeceğiz. Ve bu güncellemeden ve zaman harcamasından fayda görmüyorum.

Servis Bulucu yaklaşımını savunmaya çalışmıyorum. Ama bu yanlış anlama beni çok önemli bir şey kaybettiğimi düşündürüyor. Birisi şüphelerimi giderebilir mi?

GÜNCELLEME (ÖZET):

"Servis Bulucu bir anti-model mi?" Sorumun cevabı gerçekten koşullara bağlı. Ve kesinlikle araç listenizden çıkarmanızı önermem. Eski kodla uğraşmaya başladığınızda çok kullanışlı olabilir. Projenizin en başında olmak için yeterince şanslıysanız, DI yaklaşımı Servis Bulucu'ya göre bazı avantajları olduğundan daha iyi bir seçim olabilir.

Ve beni yeni projelerim için Servis Bulucu'yu kullanmamaya ikna eden temel farklar:

  • En belirgin ve önemli: Servis Bulucu sınıf bağımlılıklarını gizler
  • Bazı IoC kapsayıcısı kullanıyorsanız, tüm bağımlılıkları doğrulamak ve eksik eşlemeler (veya yanlış yapılandırma) hakkında anında geri bildirim vermek için başlangıçta tüm kurucuları tarar; IoC kapsayıcınızı Servis Bulucu olarak kullanıyorsanız bu mümkün değildir

Ayrıntılar için aşağıda verilen mükemmel cevapları okuyun.


"O sınıfı başlatan tüm testleri güncellememiz gerekmeyecek mi?" Testlerinizde bir inşaatçı kullanıyorsanız mutlaka doğru değildir. Bu durumda, yalnızca oluşturucuyu güncellemeniz gerekir.
Peter Karlsson

Haklısın, duruma bağlı. Örneğin, büyük Android uygulamalarında, düşük özellikli mobil cihazlardaki performans endişeleri nedeniyle insanlar DI'yi kullanmak konusunda çok isteksiz davrandılar. Bu gibi durumlarda, hala test edilebilir kod yazmak için bir alternatif bulmak zorundasınız ve Servis Bulucu'nun bu durumda yeterince iyi bir yedek olduğunu söyleyebilirim. (Not: Yeni Dagger 2.0 DI çerçevesi yeterince olgun olduğunda Android için işler değişebilir.)
G. Lombard

1
Bu soru gönderildiği için, Mark Seemann'ın Servis Bulucu'da , en iyi argümanı olan enkapsülasyonu (ve tüm semptomların altında yatan) kapsülleme kırılarak hizmet bulucunun OOP'yi nasıl ihlal ettiğini açıklayan bir Anti-Pattern yazısı olduğuna dikkat edin. önceki tüm argümanlarda kullandığı). 2015-10-26 Güncellemesi: Servis Bulucu ile ilgili temel sorun kapsüllemenin ihlali .
NightOwl888

Yanıtlar:


125

Desenleri, sadece uymadığı bazı durumlar olduğu için anti-desen olarak tanımlarsanız, EVET bu bir anti desendir. Fakat bu akıl yürütme ile tüm kalıplar aynı zamanda anti kalıplar olacaktır.

Bunun yerine, kalıpların geçerli kullanımları olup olmadığına bakmalıyız ve Servis Bulucu için birkaç kullanım durumu vardır. Ama verdiğiniz örneklere bakarak başlayalım.

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

Bu sınıfla ilgili bakım kabusu, bağımlılıkların gizlenmesidir. Bu sınıfı oluşturup kullanıyorsanız:

var myType = new MyType();
myType.MyMethod();

Hizmet konumu kullanılarak gizlenmişlerse bağımlılıkları olduğunu anlamıyorsunuz. Şimdi, bunun yerine bağımlılık enjeksiyonunu kullanırsak:

public class MyType
{
    public MyType(IDep1 dep1, IDep2 dep2)
    {
    }

    public void MyMethod()
    {
        dep1.DoSomething();

        // new dependency
        dep2.DoSomething();
    }
}

Bağımlılıkları doğrudan tespit edebilir ve sınıfları tatmin etmeden kullanamazsınız.

Tipik bir iş uygulaması hattında, bu nedenle hizmet konumunun kullanımından kaçınmalısınız. Başka seçenek olmadığında kullanılacak desen olmalıdır.

Desen bir anti-desen midir?

Hayır.

Örneğin, kontrol konteynerlerinin ters çevrilmesi servis konumu olmadan çalışmaz. Hizmetleri dahili olarak bu şekilde çözüyorlar.

Ancak çok daha iyi bir örnek ASP.NET MVC ve WebApi'dir. Kontrolörlerde bağımlılık enjeksiyonunu mümkün kılan nedir? Bu doğru - servis yeri.

Sorularınız

Ama bir saniye bekleyin, eğer DI yaklaşımını kullanıyor olsaydık, yapıcıdaki başka bir parametreye (yapıcı enjeksiyonu durumunda) bir bağımlılık getirirdik. Ve sorun hala orada olacak.

İki ciddi sorun daha var:

  1. Servis konumu ile başka bir bağımlılık daha ekliyorsunuz: Servis bulucu.
  2. Bağımlılıkların hangi yaşam süresine sahip olması gerektiğini ve nasıl / ne zaman temizlenmesi gerektiğini nasıl anlarsınız?

Bir kap kullanarak yapıcı enjeksiyonu ile bunu ücretsiz olarak elde edersiniz.

ServiceLocator kurmayı unutabilirsek, IoC kapsayıcımıza yeni bir eşleme eklemeyi unutabiliriz ve DI yaklaşımı aynı çalışma zamanı sorununa sahip olur.

Bu doğru. Ancak yapıcı enjeksiyonu ile hangi bağımlılıkların eksik olduğunu anlamak için tüm sınıfı taramanız gerekmez.

Bazı daha iyi kaplar da başlangıçta tüm bağımlılıkları doğrular (tüm kurucuları tarayarak). Yani bu kaplarda, daha sonraki geçici bir noktada değil, doğrudan çalışma zamanı hatası alırsınız.

Ayrıca, yazar birim test zorluklarından bahsetti. Ancak, DI yaklaşımıyla ilgili sorunlarımız olmayacak mı?

Hayır. Statik bir servis bulucuya bağımlı olmadığınız için. Statik bağımlılıklarla çalışan paralel testler yapmaya çalıştınız mı? Eğlenceli değil.


2
Jgauffin, cevabınız için teşekkürler. Başlangıçta otomatik kontrol hakkında önemli bir noktayı işaret ettiniz. Bunu düşünmedim ve şimdi DI'nin başka bir yararı görüyorum. Ayrıca bir örnek verdiniz: "var myType = new MyType ()". Ancak, gerçek bir uygulamada bağımlılıkları asla somutlaştırmadığımız için geçerli bir sayı sayamam (IoC Container bunu her zaman bizim için yapıyor). Yani: MVC uygulamasında IMyService ve MyServiceImpl'e bağlı olan bir denetleyicimiz var, IMyRepository'ye bağlı. MyRepository ve MyService'i asla başlatmayacağız. Ctor parametrelerinden (ServiceLocator gibi) örnekler alır ve kullanırız. Öyle değil mi?
davidoff

33
Servis Bulucu'nun bir anti-desen olmama konusundaki tek argümanınız: "kontrol kaplarının ters çevrilmesi, servis konumu olmadan çalışmaz". Hizmet Bulucu niyetleri hakkında değil mekaniği hakkında olduğundan açıkça açıklandığı gibi bu argüman Ancak, geçersiz burada Mark Seemann: "Bir Kompozisyon Root kapsüllü bir DI konteyner Servis Bulucu değil - bir altyapı bileşeni bu."
Steven

4
@jgauffin Web API, Denetleyicilere DI için Hizmet Konumu kullanmaz. Hiç DI yapmaz. Yaptığı şey: Yapılandırma Hizmetlerine geçmek için kendi ControllerActivator'ınızı oluşturma seçeneği sunar. Oradan, Saf DI veya Kaplar olsun, bir kompozisyon kökü oluşturabilirsiniz. Ayrıca, Servis Konumunun kullanımını, kalıbınızın bir bileşeni olarak, "Servis Konum Belirleyici Kalıbı" tanımıyla karıştırıyorsunuz. Bu tanımla, Kompozit Kök DI bir "Hizmet Konum Belirleyici Kalıbı" olarak düşünülebilir. Yani bu tanımın bütün mesele tartışmalıdır.
Suamere

1
Bunun genel olarak iyi bir cevap olduğunu belirtmek istiyorum, sadece yaptığınız yanıltıcı bir noktaya yorum yaptım.
Suamere

2
@jgauffin DI ve SL, IoC'nin sürüm yorumlamalarıdır. SL bunu yapmanın yanlış yoludur. Bir IoC kabı SL olabilir veya DI kullanabilir. Nasıl bağlandığına bağlıdır. Ama SL kötü, kötü kötü. Her şeyi sıkıca birleştirdiğiniz gerçeğini gizlemenin bir yolu.
MirroredFate

37

Ayrıca, eski kodu yeniden ayarlıyorsanız, Servis Bulma deseninin sadece bir anti-desen değil, aynı zamanda pratik bir gereklilik olduğunu da belirtmek isterim. Hiç kimse milyonlarca kod satırı üzerinde sihirli bir değnek sallamayacak ve birdenbire tüm bu kod DI hazır olacak. Bu nedenle, DI'yi mevcut bir kod tabanına tanıtmaya başlamak istiyorsanız, genellikle yavaş yavaş DI hizmetleri olacak şeyleri değiştireceksiniz ve bu hizmetlere başvuran kod genellikle DI hizmetleri OLMAYACAKTIR. Bu nedenle, bu hizmetlerin DI kullanımına dönüştürülmüş olan servislerin örneklerini almak için Servis Bulucu'yu kullanmaları gerekecektir.

Bu nedenle, DI kavramlarını kullanmaya başlamak için büyük eski uygulamaları yeniden düzenlerken, Servis Bulucu'nun bir anti-desen DEĞİL olduğunu, aynı zamanda DI kavramlarını kademeli olarak kod tabanına uygulamanın tek yolu olduğunu söyleyebilirim.


12
Eski kodla uğraşırken, ara (ve kusurlu) adımlar atmak anlamına gelse bile, her şey sizi bu karmaşadan çıkarmak için haklıdır. Servis Bulucu böyle bir ara adımdır. Belgelenmiş, tekrarlanabilir ve etkili olduğu kanıtlanmış iyi bir alternatif çözümün var olduğunu hatırladığınız sürece, o cehennemden bir adım çıkmanıza izin verir . Bu alternatif çözüm Bağımlılık Enjeksiyonudur ve bu nedenle Servis Bulucu hala bir anti-desendir; düzgün tasarlanmış yazılım bunu kullanmaz.
Steven

RE: "Eski kod ile uğraşırken, her şeyi o karmaşadan kurtulmak için haklı" Bazen merak ediyorum sadece biraz eski kod var mı, ama biz düzeltmek için bir şey haklı olabilir çünkü bir şekilde bunu asla başaramadı.
Drew Delano

8

Test açısından Servis Bulucu kötüdür. Bkz. Misko Hevery'nin Google Tech Talk 8: 45'ten başlayan kod örnekleriyle güzel açıklaması http://youtu.be/RlfLCWKxHJ0 . Onun benzetmesini beğendim: 25 dolara ihtiyacınız varsa, cüzdanınızı paranın alınacağı yerden vermek yerine doğrudan para isteyin. Ayrıca Servis Bulucu'yu ihtiyacınız olan iğneye sahip bir samanlık ile karşılaştırır ve nasıl alınacağını bilir. Service Locator kullanan sınıfların bu nedenle yeniden kullanılması zordur.


10
Bu geri dönüştürülmüş bir görüş ve yorum olarak daha iyi olurdu. Ayrıca, onun benzetmesinin, bazı modellerin bazı problemler için diğerlerine göre daha uygun olduğunu kanıtlamaya hizmet ettiğini düşünüyorum.
8bitjunkie

6

Bakım sorunu (beni şaşırtan)

Bu konuda servis bulucu kullanımının kötü olmasının 2 farklı nedeni vardır.

  1. Örneğinizde, servis bulucuya statik bir başvuruyu sınıfınıza sabit olarak kodluyorsunuz. Bu , sınıfınızı doğrudan servis bulucuya sıkıca bağlar, bu da servis bulucu olmadan işlev görmeyeceği anlamına gelir . Ayrıca, birim testleriniz (ve sınıfı kullanan herkes) de örtük olarak servis bulucuya bağlıdır. Burada fark edilmemiş gibi görünen bir şey, yapıcı enjeksiyonunu kullanırken birim testi yaparken bir DI kabına ihtiyacınız olmamasıdır. ; bu da birim testlerinizi (ve geliştiricilerin bunları anlama yeteneğini) oldukça basitleştirir. Bu, yapıcı enjeksiyonunu kullanarak elde ettiğiniz birim test avantajıdır.
  2. Yapımcı Intellisense'in neden önemli olduğuna gelince, buradaki insanlar noktayı tamamen kaçırmış görünüyorlar. Bir sınıf bir kez yazılır, ancak çeşitli uygulamalarda (yani birkaç DI yapılandırmasında) kullanılabilir . Zamanla, (umarım güncel) belgelere bakmak veya başarısız olursa orijinal kaynak koduna ( kullanışlı olun) bir sınıfın bağımlılıklarının ne olduğunu belirlemek için. Servis bulucuya sahip sınıfın yazılması genellikle daha kolaydır , ancak projenin devam eden bakımında bu kolaylığın bedelini ödemekle kalmazsınız.

Basit ve basit: İçinde servis bulucu bulunan bir sınıfı yeniden kullanmak daha zordur , yapıcısı aracılığıyla bağımlılıklarını kabul eden .

Şu adresten bir hizmeti kullanmanız gereken durumu düşünün: LibraryA yazarı kullanırsınız karar verdiğini ServiceLocatorAve bir hizmet LibraryBolan yazar kullanmaya karar verdi ServiceLocatorB. Projemizde 2 farklı servis bulucu kullanmaktan başka seçeneğimiz yok. İyi bir dokümantasyon, kaynak kodu veya hızlı arama yazarı yoksa, kaç bağımlılığın yapılandırılması gerektiğini tahmin eden bir oyundur. Bu seçeneklerin başarısız olması durumunda, yalnızca bir dekoder kullanmamız gerekebilirbağımlılıkların ne olduğunu anlamak. Tamamen farklı 2 servis bulucu API'sı yapılandırmamız gerekebilir ve tasarıma bağlı olarak, mevcut DI kabınızı sarmanız mümkün olmayabilir. İki kütüphane arasında bir bağımlılığın bir örneğini paylaşmak hiç mümkün olmayabilir. Eğer servis bulucuları gerçekten ihtiyaç duyduğumuz servislerle aynı kütüphanelerde bulunmuyorlarsa, projenin karmaşıklığı daha da karmaşıklaşabilir - dolaylı olarak ek kütüphane referanslarını projemize sürükleriz.

Şimdi yapıcı enjeksiyonu ile yapılan aynı iki hizmeti düşünün. Referans ekleyinLibraryA . İçin bir referans ekleyin LibraryB. DI yapılandırmanızdaki bağımlılıkları sağlayın (Intellisense aracılığıyla neyin gerekli olduğunu analiz ederek). Bitti.

Mark Seemann, bu avantajı grafik biçiminde açıkça gösteren bir StackOverflow cevabına sahiptir , bu sadece başka bir kütüphaneden bir servis bulucu kullanırken değil, aynı zamanda hizmetlerde yabancı varsayılanları kullanırken de geçerlidir.


1

Bilgim bunu değerlendirecek kadar iyi değil, ama genel olarak, bir şeyin belirli bir durumda bir kullanımı varsa, bunun bir anti-desen olamayacağı anlamına gelmez. Özellikle, 3. taraf kütüphanelerle uğraşırken, tüm yönler üzerinde tam kontrole sahip değilsiniz ve sonuçta en iyi olmayan çözümü kullanabilirsiniz.

İşte Adaptif Kod C # Üzerinden Bir Paragraf :

"Ne yazık ki, servis bulucu bazen kaçınılmaz bir anti-modeldir. Bazı uygulama türlerinde - özellikle Windows İş Akışı Vakfı - altyapı, yapıcı enjeksiyonuna uygun değildir. Bu durumlarda, tek alternatif bir servis bulucu kullanmaktır. (anti-) paterne karşı tüm vitriollerim için, manuel olarak bağımlılıklar inşa etmekten sonsuza kadar daha iyidir.Nihayetinde, dekoratörler, adaptörler, ve benzer faydalar. "

- Hall, Gary McLean. C # ile Uyarlamalı Kod: Tasarım desenleri ve SOLID ilkeleri ile çevik kodlama (Geliştirici Referansı) (s. 309). Pearson Eğitim.


0

Yazar "derleyici size yardımcı olmaz" nedenleri - ve bu doğrudur. Bir sınıfı tenezzül ettirdiğinizde, arayüzünü dikkatlice seçmek isteyeceksiniz - diğer hedeflerin yanı sıra onu mantıklı olduğu kadar bağımsız hale getirmek ...

İstemcinin, açık arabirim aracılığıyla bir hizmete (bağımlılığa) başvuruyu kabul etmesini sağlayarak,

  • dolaylı olarak kontrol olsun, böylece derleyici "yardımcı olur".
  • Ayrıca, müşterinin "Konum Belirleyici" veya benzer mekanizmalar hakkında bir şeyler bilmesi ihtiyacını da ortadan kaldırıyorsunuz, böylece müşteri aslında daha bağımsız.

DI'nin sorunları / dezavantajları olduğu konusunda haklısınız, ancak belirtilen avantajlar onlara çok daha ağır basmaktadır ... IMO. Haklısınız, DI ile arayüzde (yapıcı) bir bağımlılık var - ama umarım bu ihtiyacınız olan ve görünür ve kontrol edilebilir hale getirmek istediğiniz bağımlılıktır.


Zrin, düşüncelerin için teşekkürler. Anladığım kadarıyla, "uygun" bir DI yaklaşımı ile birim testleri dışında hiçbir yerde bağımlılıklarımı örneklememeliyim. Yani derleyici bana sadece testlerimle yardımcı olacak. Ama asıl sorumda açıkladığım gibi, kırık testlerle bu "yardım" bana hiçbir şey vermiyor. Yapar?
davidoff

'Statik bilgi' / 'derleme zamanı kontrolü' argümanı saman bir adamdır. @Davidoff'un işaret ettiği gibi, DI çalışma zamanı hatalarına eşit derecede duyarlıdır. Ayrıca, modern IDE'lerin üyeler için yorum / özet bilgilerinin araç ipucu görünümlerini sağladığını ve hatta olmayanlarda bile, birileri API'yı sadece 'tanıyana kadar hala belgelere bakacağını da ekleyeceğim. Belgeler, gerekli bir kurucu parametresi veya bir bağımlılığın nasıl yapılandırılacağı hakkında bir açıklama olsun, belgelerdir.
tuespetre

Uygulama / kodlama ve kalite güvencesi hakkında düşünmek - kodun okunabilirliği çok önemlidir - özellikle arayüz tanımı için. Otomatik derleme zamanı kontrolleri olmadan yapabiliyorsanız ve arayüzünüzü yeterince yorumluyorsanız / belgelendirirseniz, sanırım kolayca görülebilen / öngörülemeyen içeriğe sahip bir tür küresel değişkene gizli bağımlılığın dezavantajlarını en azından kısmen çözebilirsiniz. Bu dezavantajlardan ağır basan bu modeli kullanmak için iyi nedenlere ihtiyacınız olduğunu söyleyebilirim.
Zrin

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.