Bağımlılık tersine çevirme ilkesi nedir ve neden önemlidir?


178

Bağımlılık tersine çevirme ilkesi nedir ve neden önemlidir?



3
Vikipedi'deki "yüksek seviye" ve "düşük seviye" terimlerini kullanarak burada çok saçma cevaplar. Bu terimlere erişilemez ve çok sayıda okuyucu bu sayfaya yönlendirilir. Vikipedi'yi yeniden düzenleyecekseniz, bağlam vermek için lütfen cevaplarınızda bu terimleri tanımlayın !
8bitjunkie

Yanıtlar:


107

Bu belgeye göz atın: Bağımlılık Ters Çevirme İlkesi .

Temelde şöyle diyor:

  • Yüksek seviyeli modüller, düşük seviyeli modüllere bağlı olmamalıdır. Her ikisi de soyutlamalara bağlı olmalıdır.
  • Soyutlamalar asla detaylara bağlı olmamalıdır. Ayrıntılar soyutlamalara bağlı olmalıdır.

Kısaca neden önemli olduğu konusunda: değişiklikler risklidir ve bir uygulama yerine bir konsepte bağlı olarak, çağrı sitelerinde değişiklik ihtiyacını azaltırsınız.

Etkili olarak, DIP farklı kod parçaları arasındaki bağlantıyı azaltır. Buradaki fikir, bir kayıt tesisini uygulamanın birçok yolu olmasına rağmen, onu kullanma şeklinizin zaman içinde nispeten kararlı olması gerektiğidir. Günlük tutma kavramını temsil eden bir arabirim ayıklayabiliyorsanız, bu arabirim zaman içinde uygulanmasından çok daha kararlı olmalı ve çağrı siteleri bu günlükleme mekanizmasını korurken veya genişletirken yapabileceğiniz değişikliklerden çok daha az etkilenmelidir.

Uygulamanın bir arabirime bağlı olmasını sağlayarak, çalışma zamanında hangi uygulamanın kendi ortamınız için daha uygun olduğunu seçme olanağına sahip olursunuz. Olgulara bağlı olarak, bu da ilginç olabilir.


31
Bu cevap DIP'nin neden önemli olduğunu veya hatta DIP'nin ne olduğunu söylemez. Resmi DIP belgesini okudum ve bunun gerçekten kötü ve haksız bir "ilke" olduğunu düşünüyorum, çünkü kusurlu bir varsayım temel alıyor: yüksek seviyeli modüller tekrar kullanılabilir.
Rogério

3
Bazı nesneler için bir bağımlılık grafiği düşünün. Nesnelere DIP uygulayın. Şimdi herhangi bir nesne, diğer nesnelerin uygulanmasına bağımlı olacaktır. Birim testi artık basit. Daha sonra yeniden kullanım için yeniden düzenleme mümkündür. Tasarım değişiklikleri çok sınırlı değişiklik kapsamlarına sahiptir. Tasarım problemleri artmıyor. Ayrıca veri güvenilirliğini tersine çevirme için AI modeli "Blackboard" a bakınız. Birlikte, yazılımı anlaşılır, sürdürülebilir ve güvenilir kılmak için çok güçlü araçlar. Bu bağlamda bağımlılık enjeksiyonunu göz ardı edin. İlgisiz.
Tim Williscroft

6
B sınıfı kullanan A sınıfı, A'nın B'nin uygulanmasına "bağlı" olduğu anlamına gelmez; yalnızca B'nin genel arayüzüne bağlıdır. Hem A hem de B'nin artık sadece bağımlı olduğu ayrı bir soyutlama eklemek, A'nın artık B'nin ortak arayüzüne derleme zamanı bağımlılığına sahip olmayacağı anlamına gelir. Birimler arasındaki izolasyon, bu ekstra soyutlamalar olmaksızın kolayca gerçekleştirilebilir; Java ve .NET'te tüm durumlarla (statik yöntemler, yapıcılar vb.) ilgilenen belirli alay araçları vardır. DIP uygulaması, yazılımı daha karmaşık ve daha az bakım yapılabilir hale getirir ve daha test edilemez hale gelir.
Rogério

1
O zaman kontrolün ters çevrilmesi nedir? Bağımlılığı İnversiyonu elde etmenin bir yolu?
user20358

2
@Rogerio, aşağıdaki Derek Greer tepkisine bakın, açıklıyor. AFAIK, DIP, A'nın neye ihtiyaç duyduğunu söyleyen B'nin değil, A'nın neye ihtiyacı olduğunu A'nın söylediğini söylüyor. Bu nedenle, A'nın ihtiyaç duyduğu arayüz B tarafından değil, A için verilmelidir.
ejaenv

145

Çevik Yazılım Geliştirme, İlkeler, Desenler ve Uygulamaları ve C # 'daki Çevik İlkeler, Desenler ve Uygulamalar kitapları, Bağımlılık Tersine Çevirme İlkesinin arkasındaki orijinal hedefleri ve motivasyonları tam olarak anlamak için en iyi kaynaklardır. "Bağımlılık Ters Çevirme İlkesi" makalesi de iyi bir kaynak olmakla birlikte, daha önce bahsedilen kitaplara yol açan bir taslağın yoğunlaştırılmış bir versiyonu olması nedeniyle, bu ilkeyi ayırt etmenin anahtarı olan paket ve arayüz sahipliği Tasarım Paternleri (Gamma, et al.) kitabında bulunan "bir uygulamaya değil bir arayüze programla" nın daha genel olarak tavsiye edilmesini öneriyor.

Bir özet vermek gerekirse, Bağımlılık Ters Çevirme Prensibi temel olarak geleneksel bağımlılık yönünün "daha yüksek seviye" bileşenlerden "daha düşük seviye" bileşenlere geri döndürülmesiyle ilgilidir , öyle ki "daha düşük seviye" bileşenler "daha yüksek seviye" bileşenlerin sahip olduğu arayüzlere bağımlıdır . (Not: "üst düzey" bileşen, burada katmanlı bir mimari içindeki kavramsal konumunu değil, harici bağımlılık / hizmet gerektiren bileşeni ifade eder.) Bunu yaparken, bağlantı teorik olarak bileşenlerden kaydırıldığı kadar azalmaz teorik olarak daha değerli bileşenler için daha az değerli.

Bu, dış bağımlılıkları, bileşenin tüketicisi tarafından bir uygulamanın sağlanması gereken bir arabirim olarak ifade edilen bileşenler tasarlanarak elde edilir. Başka bir deyişle, tanımlanmış arabirimler bileşeni nasıl kullandığınızı değil, bileşen için gerekli olanları ifade eder (örn. "IDoSomething" değil "INeedSomething").

Bağımlılık Tersine Çevirme İlkesinin bahsetmediği şey, arayüzlerin kullanımı yoluyla bağımlılıkları soyutlamanın basit bir uygulamasıdır (örn. MyService → [ILogger ⇐ Logger]). Bu, bir bileşeni bağımlılığın özel uygulama detayından ayırırken, tüketici ile bağımlılık arasındaki ilişkiyi tersine çevirmez (örneğin [MyService → IMyServiceLogger] ⇐ Kaydedici.

Bağımlılık Tersine Çevirme İlkesinin önemi, işlevlerinin bir kısmı (günlük kaydı, doğrulama vb.) İçin harici bağımlılıklara dayanan yazılım bileşenlerini yeniden kullanabilme özelliğine kadar damıtılabilir.

Bu genel yeniden kullanım hedefi içinde, iki alt yeniden kullanım türünü tanımlayabiliriz:

  1. Alt bağımlılık uygulamaları olan birden fazla uygulamada bir yazılım bileşeni kullanma (örneğin, bir DI kapsayıcısı geliştirdiniz ve günlüğe kaydetme sağlamak istiyorsunuz, ancak kapsayıcıyı kullanan herkesin de yapması gereken kapsayıcıyı belirli bir kayıt cihazıyla eşleştirmek istemiyorsunuz) seçtiğiniz günlük kitaplığını kullanın).

  2. Yazılım bileşenlerini gelişen bir bağlamda kullanma (örneğin, uygulamanın ayrıntılarının geliştiği birden çok sürümünde aynı kalan iş mantığı bileşenleri geliştirdiniz).

Altyapı kitaplığı gibi birden çok uygulamada bileşenleri yeniden kullanmanın ilk örneğinde amaç, tüketicilerinizi kendi kitaplığınızın alt bağımlılıklarına bağlamadan tüketicilerinize temel bir altyapı gereksinimi sağlamaktır; tüketicilerin de aynı bağımlılıklara ihtiyacı var. Bu, kitaplığınızın tüketicileri aynı altyapı ihtiyaçları için farklı bir kitaplık kullanmayı seçtiğinde (ör. NLog vs. log4net) veya gerekli kitaplığın sürümle geriye dönük olarak uyumlu olmayan sonraki bir sürümünü kullanmayı seçtiklerinde sorunlu olabilir. kütüphaneniz tarafından isteniyor.

İkinci iş mantığı bileşenlerini (örn. "Üst düzey bileşenler") yeniden kullanmanın amacı ile, uygulamanızın temel etki alanı uygulamasını uygulama ayrıntılarınızın değişen gereksinimlerinden (örn. Kalıcılık kütüphanelerini, mesajlaşma kütüphanelerini değiştirme / yükseltme) izole etmektir. , şifreleme stratejileri vb.). İdeal olarak, bir uygulamanın uygulama ayrıntılarının değiştirilmesi uygulamanın iş mantığını çevreleyen bileşenleri kırmamalıdır.

Not: Bazıları bu ikinci vakayı gerçek yeniden kullanım olarak tanımlamaya itiraz edebilir, tek bir gelişen uygulamada kullanılan iş mantığı bileşenleri gibi bileşenlerin yalnızca tek bir kullanımı temsil ettiğini düşünebilir. Bununla birlikte, buradaki fikir, uygulamanın uygulama detaylarındaki her değişikliğin yeni bir bağlam ve dolayısıyla farklı bir kullanım durumu oluşturmasıdır, ancak nihai hedefler izolasyon ve taşınabilirlik olarak ayırt edilebilir.

Bu ikinci durumda Bağımlılık Tersinirme İlkesi'ni takip etmek bazı faydalar sunabilirken, Java ve C # gibi modern dillere uygulanan değerinin, belki de alakasız olma noktasına kadar çok azaldığı belirtilmelidir. Daha önce de tartışıldığı gibi, DIP, uygulama detaylarının tamamen ayrı paketlere ayrılmasını içerir. Ancak, gelişen bir uygulama söz konusu olduğunda, yalnızca iş alanı açısından tanımlanan arayüzlerin kullanılması, uygulama ayrıntıları nihai olarak aynı paket içinde yer alsa bile, uygulama detay bileşenlerinin değişen ihtiyaçları nedeniyle daha üst düzey bileşenlerin değiştirilmesi gerekliliğine karşı koruma sağlayacaktır. . İlkenin bu kısmı, ilkenin daha yeni dillerle ilgili olmayan kodlandığı (yani C ++) göz önünde bulundurularak dile ilişkin yönleri yansıtır. Bahsedilen,

Arayüzlerin basit kullanımı, Bağımlılık Enjeksiyonu ve Ayrılmış Arayüz modeli ile ilgili olduğu için bu prensibin daha uzun bir tartışması burada bulunabilir . Ek olarak, prensibin JavaScript gibi dinamik olarak yazılmış dillerle nasıl ilişkili olduğuna dair bir tartışma burada bulunabilir .


17
Teşekkürler. Şimdi cevabımın nasıl bir noktayı kaçırdığını görüyorum. MyService → [ILogger ⇐ Logger] ve [MyService → IMyServiceLogger] ⇐ Logger arasındaki fark ince fakat önemlidir.
Patrick McElhaney

2
Aynı satırda burada çok iyi açıklanmıştır: lostechies.com/derickbailey/2011/09/22/…
ejaenv

1
İnsanların, bağımlılık enjeksiyonu için kanonik kullanım alanı olarak kaydedicileri kullanmayı bırakmasını nasıl istedim. Özellikle log4net ile bağlantılı olarak neredeyse anti-desen. Bu bir yana, kickass açıklaması!
Casper Leon Nielsen

4
@ Casper Leon Nielsen - DIP'nin DI ile bir ilgisi yok Bunlar eş anlamlı veya eşdeğer kavramlar değil.
TSmith

4
@ VF1 Özet paragrafta belirtildiği gibi, Bağımlılık Tersine Çevirme İlkesinin önemi öncelikle yeniden kullanılır. John, bir günlük kitaplığına dış bağımlılığı olan bir kitap yayınlar ve Sam, John'un kitaplığını kullanmak isterse, Sam geçici günlük bağımlılığını üstlenir. Sam, John'un günlük kütüphanesi seçilmeden uygulamasını asla konuşlayamayacak. Ancak John DIP'yi takip ederse, Sam bir adaptör sağlamakta ve seçtiği günlük kütüphanesini kullanmakta serbesttir. DIP kolaylık değil, eşleştirme ile ilgili.
Derek Greer

14

Yazılım uygulamaları tasarlarken, düşük seviye sınıfları temel ve birincil işlemleri uygulayan sınıfları (disk erişimi, ağ protokolleri, ...) ve yüksek seviye sınıfları karmaşık mantığı (iş akışları, ...) kapsayan sınıfları düşünebiliriz.

Sonuncusu düşük seviyeli sınıflara dayanır. Bu tür yapıları uygulamanın doğal bir yolu, düşük seviyeli sınıflar yazmak ve bir kez karmaşık yüksek seviyeli sınıfları yazmak zorunda olduğumuzdur. Üst düzey sınıflar diğerleri açısından tanımlandığından, bunu yapmanın mantıklı bir yolu gibi görünmektedir. Ancak bu esnek bir tasarım değildir. Düşük seviyeli bir sınıfı değiştirmemiz gerekirse ne olur?

Bağımlılık Ters Çevirme İlkesi şunları belirtir:

  • Yüksek seviye modülleri, düşük seviye modüllerine bağlı olmamalıdır. Her ikisi de soyutlamalara bağlı olmalıdır.
  • Soyutlamalar detaylara bağlı olmamalıdır. Ayrıntılar soyutlamalara bağlı olmalıdır.

Bu ilke, yazılımdaki yüksek seviye modüllerin alt seviye modüllere bağlı olması gerektiği konvansiyonel düşünceyi "tersine çevirmeyi" amaçlamaktadır. Burada yüksek seviyeli modüller, daha düşük seviyeli modüller tarafından uygulanan soyutlamaya (örneğin, arabirim yöntemlerine karar verme) sahiptir. Böylece daha düşük seviyeli modülleri daha yüksek seviyeli modüllere bağımlı hale getirir.


11

İyi uygulanan bağımlılık değişimi, uygulamanızın tüm mimarisi düzeyinde esneklik ve kararlılık sağlar. Uygulamanızın daha güvenli ve istikrarlı bir şekilde gelişmesine izin verecektir.

Geleneksel katmanlı mimari

Geleneksel olarak katmanlı bir mimari kullanıcı arayüzü iş katmanına bağlıydı ve bu da veri erişim katmanına bağlıydı.

Katman, paket veya kitaplığı anlamalısınız. Kodun nasıl olacağını görelim.

Veri erişim katmanı için bir kütüphane veya paketimiz olurdu.

// DataAccessLayer.dll
public class ProductDAO {

}

Ve veri erişim katmanına bağlı başka bir kütüphane veya paket katmanı iş mantığı.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

Bağımlılık tersine çevrilmiş katmanlı mimari

Bağımlılık değişimi aşağıdakileri gösterir:

Yüksek seviyeli modüller, düşük seviyeli modüllere bağlı olmamalıdır. Her ikisi de soyutlamalara bağlı olmalıdır.

Soyutlamalar detaylara bağlı olmamalıdır. Ayrıntılar soyutlamalara bağlı olmalıdır.

Yüksek seviye modüller ve düşük seviye nedir? Kütüphaneler veya paketler gibi düşünme modülleri, yüksek seviye modülü geleneksel olarak bağımlılıkları olan ve bağımlı oldukları düşük seviyeli modüller olacaktır.

Başka bir deyişle, modül yüksek seviyesi, eylemin çağrıldığı yer ve eylemin gerçekleştirildiği düşük düzey olacaktır.

Bu ilkeden çıkarılacak makul bir sonuç, betonlar arasında hiçbir bağımlılığın olmaması, ancak bir soyutlamaya bağımlılığın olması gerektiğidir. Ancak benimsediğimiz yaklaşıma göre, yatırım bağımlılığını değil, bir soyutlamayı yanlış uygulayabiliriz.

Kodumuzu aşağıdaki gibi uyarladığımızı düşünün:

Veri erişim katmanı için soyutlamayı tanımlayan bir kütüphane ya da paketimiz olurdu.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

Ve veri erişim katmanına bağlı başka bir kütüphane veya paket katmanı iş mantığı.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

Her ne kadar iş ve veri erişimi arasındaki soyutlamaya bağımlı olsak da aynı kalır.

Bağımlılığın tersine çevrilmesi için, kalıcılık arabiriminin, bu yüksek düzey mantığın veya etki alanının düşük düzey modülünde olmadığı modül veya pakette tanımlanması gerekir.

İlk olarak, etki alanı katmanının ne olduğunu tanımlayın ve iletişiminin soyutlanması kalıcılık olarak tanımlanır.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

Kalıcılık katmanı etki alanına bağlı olduktan sonra, bir bağımlılık tanımlanırsa şimdi tersine çevrilir.

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(kaynak: xurxodev.com )

İlkenin derinleştirilmesi

Amacı ve faydaları derinleştirerek kavramı iyi asimile etmek önemlidir. Mekanik olarak kalır ve tipik vaka deposunu öğrenirsek, bağımlılık ilkesini nerede uygulayabileceğimizi belirleyemeyiz.

Ama neden bir bağımlılığı tersine çeviriyoruz? Belirli örneklerin ötesindeki temel amaç nedir?

Bu genellikle daha az kararlı şeylere bağlı olmayan en kararlı şeylerin daha sık değişmesine izin verir.

Kalıcılık türünün değiştirilmesi için, veritabanı veya teknoloji aynı veritabanına erişmek için etki alanı mantığından veya kalıcılıkla iletişim kurmak için tasarlanmış eylemlerden daha kolaydır. Bu nedenle, bağımlılık tersine çevrilir, çünkü bu değişiklik meydana gelirse sürekliliği değiştirmek daha kolaydır. Bu şekilde alan adını değiştirmek zorunda kalmayacağız. Etki alanı katmanı en kararlı olanıdır, bu yüzden hiçbir şeye bağlı olmamalıdır.

Ancak sadece bu depo örneği yoktur. Bu ilkenin uygulandığı birçok senaryo vardır ve bu prensibe dayanan mimariler vardır.

Mimarileri

Bağımlılığın ters çevrilmesinin tanımının anahtarı olduğu mimariler vardır. Tüm alanlarda en önemlisidir ve etki alanı ile paketlerin veya kitaplıkların geri kalanı arasındaki iletişim protokolünü tanımlayan soyutlamalardır.

Temiz Mimari

Gelen Temiz mimarisi alanı merkezinde bulunan ve bağımlılık gösteren oklar yönünde bakarsak, en önemli ve istikrarlı katmanları nelerdir açıktır edilir. Dış katmanlar dengesiz araçlar olarak kabul edilir, bu nedenle bunlara bağlı olmaktan kaçının.


(kaynak: 8thlight.com )

Altıgen Mimari

Alanın aynı zamanda orta kısımda bulunduğu ve bağlantı noktalarının dominodan dışarıya iletişim soyutlamaları olduğu altıgen mimari ile aynı şekilde gerçekleşir. Burada yine alanın en istikrarlı olduğu ve geleneksel bağımlılığın tersine çevrildiği açıktır.


Ne demek istediğinden emin değilim, ama bu cümle tutarsız: "Ama aldığımız yaklaşıma göre yatırım bağımlılığını değil, soyutlamayı yanlış uygulayabiliriz." Belki düzeltmek istersiniz?
Mark Amery

9

Benim için, resmi makalede açıklandığı gibi, Bağımlılık Tersine Çevirme İlkesi, C ++ dilinde bir sorunu çözmenin bir yolu olduğu gibi, doğası gereği daha az yeniden kullanılabilir olan modüllerin tekrar kullanılabilirliğini artırmaya yönelik yanlış yönlendirilmiş bir girişimdir.

C ++ ile ilgili sorun, başlık dosyalarının genellikle özel alan ve yöntem bildirimleri içermesidir. Bu nedenle, üst düzey bir C ++ modülü, düşük düzeyli bir modülün başlık dosyasını içeriyorsa, bu modülün gerçek uygulama ayrıntılarına bağlı olacaktır . Ve bu açıkçası iyi bir şey değil. Ancak bu, günümüzde yaygın olarak kullanılan daha modern dillerde bir sorun değildir.

Yüksek seviyeli modüller, düşük seviyeli modüllere göre doğal olarak daha az yeniden kullanılabilirler çünkü birincisi normalde ikincisinden daha fazla uygulamaya / bağlama özgüdür. Örneğin, bir UI ekranı uygulayan bir bileşen en yüksek seviyededir ve aynı zamanda uygulamaya (tamamen?) Özeldir. Böyle bir bileşeni farklı bir uygulamada tekrar kullanmaya çalışmak karşı üretkendir ve sadece aşırı mühendisliğe yol açabilir.

Bu nedenle, B bileşenine (A'ya bağlı olmayan) bağlı olan A bileşeninin aynı seviyesinde ayrı bir soyutlamanın oluşturulması, ancak A bileşeni gerçekten farklı uygulamalarda veya bağlamlarda yeniden kullanım için faydalı olacaksa yapılabilir. Durum böyle değilse, DIP uygulamak kötü bir tasarım olacaktır.


Üst düzey soyutlama yalnızca bu uygulama bağlamında yararlı olsa bile, gerçek uygulamayı test etmek için bir saplama ile değiştirmek istediğinizde değeri olabilir (veya genellikle web'de bulunan bir uygulamaya komut satırı arabirimi sağlamak)
Przemek Pokrywka

3
DIP, diller arasında önemlidir ve C ++ ile ilgisi yoktur. Yüksek seviye kodunuz asla uygulamanızdan ayrılmasa bile, DIP daha düşük seviye kodu eklediğinizde veya değiştirdiğinizde kod değişikliğini içermenizi sağlar. Bu, hem bakım maliyetlerini hem de değişikliğin istenmeyen sonuçlarını azaltır. DIP daha üst düzey bir kavramdır. Bunu anlamadıysanız, daha fazla googling yapmanız gerekir.
Dirk Bester

3
Sanırım C ++ hakkında söylediklerimi yanlış anladınız. Bu sadece DIP için bir motivasyondu ; şüphesiz bundan daha geneldir. DIP hakkındaki resmi makalenin, merkezi motivasyonun, yüksek seviyeli modüllerin yeniden kullanılmasını destekleyerek, düşük seviyeli modüllerdeki değişikliklere karşı bağışıklık kazandıracağını açıkladığını unutmayın; yeniden kullanılmaya gerek kalmadan, aşırıya kaçma ve aşırı mühendislik olması muhtemeldir. (Bunu okudunuz mu? Ayrıca C ++ meselesinden bahsediyor.)
Rogério

1
DIP'nin ters uygulamasına bakmıyor musunuz? Yani, üst seviye modüller esasen uygulamanızı uygular. Öncelikli endişeniz onu tekrar kullanmak değil, daha düşük seviyeli modüllerin uygulamalarına daha az bağımlı hale getirmek, böylece gelecekteki değişikliklere ayak uydurmak için güncellenmesi, uygulamanızı zaman ve yeni teknolojinin yıkımından yalıtır. WindowsOS'un değiştirilmesini kolaylaştırmak değil, WindowsOS'u FAT / HDD'nin uygulama ayrıntılarına daha az bağımlı hale getirmek, böylece daha yeni NTFS / SSD'lerin WindowsOS'a hiçbir etki yaratmadan veya çok az bir etkiyle bağlanabilmesi.
knockNrod

1
UI ekranı kesinlikle en üst seviye modül değildir veya herhangi bir uygulama için olmamalıdır. En yüksek seviye modüller iş kurallarını içeren modüllerdir. En üst seviye bir modül, tekrar kullanılabilirlik potansiyeli ile tanımlanmamıştır. Lütfen bu makaledeki Bob Amca'nın basit açıklamasını kontrol edin: blog.cleancoder.com/uncle-bob/2016/01/04/…
humbaba

8

Temelde şöyle diyor:

Sınıf, belirli ayrıntılara (uygulamalara) değil, soyutlamalara (ör. Arayüz, soyut sınıflar) bağlı olmalıdır.


Bu kadar basit olabilir mi? DerekGreer'in belirttiği gibi uzun makaleler ve hatta kitaplar var mı? Aslında basit bir cevap arıyordum, ama bu kadar basitse inanılmaz: D
Darius.V

1
@ darius-v "soyutlamaları kullan" demenin başka bir versiyonu değildir. Arayüzlerin sahibi kimdir. Müdür müşteri (üst seviye bileşenler) arayüzleri tanımlamalı ve alt seviye bileşenler bunları uygulamalıdır.
boran

6

İyi cevaplar ve iyi örnekler zaten burada başkaları tarafından verilmektedir.

Nedeni DIP önemlidir o "gevşek tasarım birleştiğinde" OO-ilkesini garanti çünkü.

Yazılımınızdaki nesneler, düşük düzeyli nesnelere bağlı olarak bazı nesnelerin üst düzey olanlar olduğu bir hiyerarşiye GİRMEMELİDİR. Düşük düzeyli nesnelerde yapılan değişiklikler daha sonra üst düzey nesnelerinize kayarak yazılımı değişim için çok kırılgan hale getirir.

'Üst düzey' nesnelerinizin çok kararlı olmasını ve değişiklik için kırılgan olmamasını istiyorsunuz, bu nedenle bağımlılıkları tersine çevirmeniz gerekir.


6
Java veya .NET kodu için DIP bunu tam olarak nasıl yapıyor? Bu dillerde, C ++ 'ın aksine, düşük seviyeli bir modülün uygulanmasındaki değişiklikler, onu kullanan yüksek seviyeli modüllerde değişiklik yapılmasını gerektirmez. Yalnızca genel arayüzdeki değişiklikler dalgalanacaktır, ancak daha sonra üst düzeyde tanımlanan soyutlamanın da değişmesi gerekecektir.
Rogério

5

Bağımlılık Ters Çevirme İlkesini belirtmenin çok daha açık bir yolu:

Karmaşık iş mantığını çevreleyen modülleriniz, doğrudan iş mantığını çevreleyen diğer modüllere bağlı olmamalıdır. Bunun yerine, sadece basit verilerin arayüzlerine bağlı olmalıdırlar.

Yani, sınıfınızı Logicgenellikle insanlar gibi uygulamak yerine :

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

şöyle bir şey yapmalısın:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Datave DataFromDependencyaynı modülde yaşamalıdır Logic, onunla değil Dependency.

Bunu neden yapıyoruz?

  1. İki iş mantığı modülü şimdi ayrıştırılmıştır. DependencyDeğişiklikler yapıldığında , değiştirmeniz gerekmez Logic.
  2. Ne yaptığının anlaşılması Logicçok daha basit bir iştir: yalnızca bir ADT'ye benzeyen şey üzerinde çalışır.
  3. Logicartık daha kolay test edilebilir. Artık Datasahte verilerle doğrudan başlatabilir ve iletebilirsiniz. Alaylara veya karmaşık test iskelesine gerek yoktur.

Bunun doğru olduğunu düşünmüyorum. Eğer DataFromDependencyki doğrudan referanslar, Dependencygibi aynı modülde olduğunu Logic, daha sonra Logicmodül hala doğrudan bağlıdır Dependencyderleme zamanında modülü. Başına ilkesinin Bob Amcanın açıklama , o kaçınarak DIP tüm noktasıdır. Bunun yerine, DIP'yi takip etmek Dataiçin, aynı modülde olmalı Logic, ancak aynı modülde DataFromDependencyolmalıdır Dependency.
Mark Amery

3

Kontrolün ters çevrilmesi (IoC), bir nesnenin bağımlılığı için bir çerçeve istemekten ziyade, dışsal bir çerçeveyle bağımlılığını aldığı bir tasarım modelidir.

Geleneksel aramayı kullanan sözde kod örneği:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

IoC kullanan benzer kod:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

IoC'nin faydaları:

  • Merkezi bir çerçeveye bağımlılığınız yoktur, bu nedenle bu istenirse değiştirilebilir.
  • Nesneler enjeksiyonla, tercihen arayüzler kullanılarak oluşturulduğundan, bağımlılıkları sahte sürümlerle değiştiren birim testleri oluşturmak kolaydır.
  • Kodu ayırma.

Bağımlılık İnversiyonu Prensibi ve Kontrolün İnversiyonu aynı şey midir?
Peter Mortensen

3
DIP'deki "inversiyon" , Kontrolün İnversiyonundaki ile aynı değildir . Birincisi derleme zamanı bağımlılıklarıyla, ikincisi çalışma zamanında yöntemler arasındaki kontrol akışı ile ilgilidir.
Rogério

IoC sürümünde bir şey eksik gibi hissediyorum. Hangi Veritabanı nesnesi nasıl tanımlanır, nereden gelir? Bunun nasıl sadece dev bir tanrı egemenliğinde tüm bağımlılıkları en üst düzeye çıkarmayı geciktirmediğini anlamaya çalışıyorum
Richard Tingle

1
@ Rogério tarafından daha önce söylendiği gibi, DIP DI / IoC değildir. Bu cevap yanlış IMO
zeraDev

1

Bağımlılığın tersine çevrilmesi, yeniden kullanılabilir yazılım yapmaktır.

Fikir, birbirine dayanan iki kod parçası yerine, soyutlanmış bir arayüze güvenmeleri. Sonra herhangi bir parçayı diğeri olmadan tekrar kullanabilirsiniz.

Bunun en yaygın şekilde gerçekleştirilmesi, Java'daki Spring gibi bir kontrol (IoC) konteynerinin ters çevrilmesidir. Bu modelde, nesnelerin özellikleri, dışarı çıkıp bağımlılıklarını bulmak yerine bir XML yapılandırması aracılığıyla ayarlanır.

Bu sahte kodu düşünün ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass doğrudan Service sınıfına ve ServiceLocator sınıfına bağlıdır. Başka bir uygulamada kullanmak istiyorsanız her ikisine de ihtiyaç duyar. Şimdi bunu hayal edin ...

public class MyClass
{
  public IService myService;
}

Şimdi, MyClass tek bir arayüze, IService arayüzüne güveniyor. IoC konteynerinin aslında bu değişkenin değerini ayarlamasına izin verdik.

Şimdi, MyClass, diğer iki sınıfın bağımlılığını beraberinde getirmeden kolayca başka projelerde yeniden kullanılabilir.

Daha da iyisi, MyService'in bağımlılıklarını ve bu bağımlılıkların bağımlılıklarını sürüklemek zorunda değilsiniz ve ... iyi, fikri anlıyorsunuz.


2
Bu cevap gerçekten DIP ile ilgili değil, başka bir şeyle ilgili. İki kod parçası, soyutlanmış bir arayüze dayanabilir ve birbirlerine değil, yine de Bağımlılık Ters Çevirme İlkesine uymaz.
Rogério

1

Bir şirketteki "yüksek düzeyli" bir çalışanın planlarının yerine getirilmesi için ödenmesi ve bu planların birçok "düşük düzeyli" çalışanın planının toplu olarak uygulanmasıyla teslim edildiğini varsayabilirsek, o zaman söyleyebiliriz üst düzey çalışanın plan tanımının herhangi bir şekilde herhangi bir alt düzey çalışanın özel planına bağlanması genellikle korkunç bir plandır.

Üst düzey bir yöneticinin "teslimat süresini iyileştirmek" için bir planı varsa ve nakliye hattındaki bir çalışanın her sabah kahve içmesi ve esnemesi gerektiğini gösterirse, bu plan yüksek düzeyde birleşir ve düşük uyum gösterir. Ancak plan herhangi bir belirli çalışandan bahsetmezse ve aslında sadece "iş yapabilen bir varlık çalışmaya hazırdır" gerektiriyorsa, plan gevşek bağlanmış ve daha uyumludur: planlar üst üste gelmez ve kolayca ikame edilebilir . Müteahhitler veya robotlar kolayca çalışanların yerini alabilir ve yüksek seviyenin planı değişmeden kalır.

Bağımlılık tersine çevirme ilkesinde "yüksek seviye" "daha önemli" anlamına gelir.


1
Bu oldukça iyi bir benzetmedir. DIC kapsamında, yüksek seviyeli bileşenler hala çalışma zamanında düşük seviyeli bileşenlere bağlı olduğu gibi, bu benzetmede, yüksek seviyeli planların yürütülmesi düşük seviyeli planlara bağlıdır. Fakat aynı zamanda, DIC kapsamında olduğu gibi , derleme zamanında yüksek düzeyli planlara bağlı olan düşük düzeyli planlar gibi, benzerliklerinizde de düşük düzeyli planların oluşumunun yüksek düzeyli planlara bağlı olduğu görülmektedir.
Mark Amery

0

Yukarıdaki cevaplarda iyi bir açıklama yapıldığını görebiliyorum. Ancak basit bir örnek ile bazı kolay açıklama sağlamak istiyor.

Bağımlılık Ters Çevirme Prensibi, programcının sabit kodlanmış bağımlılıkları kaldırmasına izin verir, böylece uygulama gevşek bağlanabilir ve genişletilebilir hale gelir.

Bunu başarmak için: soyutlama yoluyla

Bağımlılık değişimi olmadan:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

Yukarıdaki kod snippet'inde adres nesnesi sabit olarak kodlanmıştır. Bunun yerine, bağımlılığı tersine çevirmeyi kullanabilir ve yapıcı veya ayarlayıcı yönteminden geçerek adres nesnesini enjekte edebilirsek. Bakalım.

Bağımlılık değişimi ile:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}

-1

Bağımlılığın tersine çevrilmesi: Somutlaşmalara değil, soyutlamalara bağlıdır.

Kontrolün ters çevrilmesi: Ana ve Soyutlamanın karşılaştırılması ve Ana'nın sistemlerin tutkalı olması.

DIP ve IoC

Bunlar bunun hakkında konuşan iyi mesajlardır:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/


-1

Diyelim ki iki sınıfımız var: Engineerve Programmer:

Sınıf Mühendisi, aşağıdaki gibi Programcı sınıfına bağımlıdır:

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

Bu örnekte sınıf Engineer, sınıfımıza bağımlıdır Programmer. Değiştirmem gerekirse ne olacak Programmer?

Açıkçası ben de değiştirmem gerekiyor Engineer. (Vay be, bu noktada OCPda ihlal ediliyor)

Peki, bu pisliği temizlemek için nelere ihtiyacımız var? Cevap aslında soyutlamadır. Soyutlama ile bu iki sınıf arasındaki bağımlılığı kaldırabiliriz. Örneğin, bir InterfaceProgramcı sınıfı için bir tane oluşturabilirim ve bundan sonra Programmer, kullanmak isteyen her sınıfın onu kullanması gerekir Interface, Sonra Programcı sınıfını değiştirerek, onu kullanan herhangi bir sınıfı değiştirmemize gerek yok Kullanılmış.

Not: DependencyInjectionyapmamızı yardımcı olabilir DIPve SRPde.


-1

Genel olarak iyi cevapların telaşına ek olarak, iyi ve kötü uygulamaları göstermek için kendime ait küçük bir örnek eklemek istiyorum. Ve evet, taş atan biri değilim!

Diyelim ki, bir dizeyi konsol G / Ç'si aracılığıyla base64 biçimine dönüştürmek istiyorsunuz . İşte saf yaklaşım:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

DIP temel olarak, yüksek seviyeli bileşenlerin düşük seviyeli uygulamaya bağlı olmaması gerektiğini söylüyor. Fakat bu çıkmazdan nasıl çıkıyorsunuz? Merkezi Kodlayıcıyı, bunların nasıl uygulandığını rahatsız etmeden sadece arayüzlere bağımlı hale getirerek:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

GoodEncoderI / O modunu değiştirmek için dokunmanız gerekmediğini unutmayın - bu sınıf bildiği I / O arayüzlerinden memnun; herhangi bir düşük seviyeli uygulaması IReadableve IWriteablehiç rahatsız etmeyecektir.


Bunun Bağımlılık Tersinmesi olduğunu düşünmüyorum. Sonuçta, burada herhangi bir bağımlılığı tersine çevirmediniz ; okuyucunuz ve yazarınız GoodEncoderikinci örneğinize bağlı değildir . Bir DIP örneği oluşturmak için, burada çıkardığınız arabirimlerin "sahip" olduğu fikrini tanıtmanız ve özellikle uygulamaları dışında kalırken GoodEncoder ile aynı pakete koymanız gerekir.
Mark Amery

-2

Bağımlılık Ters Çevirme İlkesi (DIP)

i) Yüksek seviye modüller, düşük seviye modüllere bağlı olmamalıdır. Her ikisi de soyutlamalara bağlı olmalıdır.

ii) Soyutlamalar asla detaylara bağlı olmamalıdır. Ayrıntılar soyutlamalara bağlı olmalıdır.

Misal:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

Not: Sınıf, belirli ayrıntılara değil (arayüzün uygulanması) arayüz veya soyut sınıflar gibi soyutlamalara dayanmalı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.