Bağımlılık tersine çevirme ilkesi nedir ve neden önemlidir?
Bağımlılık tersine çevirme ilkesi nedir ve neden önemlidir?
Yanıtlar:
Bu belgeye göz atın: Bağımlılık Ters Çevirme İlkesi .
Temelde şöyle diyor:
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.
Ç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:
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).
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 .
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:
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.
İ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 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 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 )
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.
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.
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 )
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.
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.
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.
İ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.
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ı Logic
genellikle 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
}
}
Data
ve DataFromDependency
aynı modülde yaşamalıdır Logic
, onunla değil Dependency
.
Bunu neden yapıyoruz?
Dependency
Değişiklikler yapıldığında , değiştirmeniz gerekmez Logic
.Logic
çok daha basit bir iştir: yalnızca bir ADT'ye benzeyen şey üzerinde çalışır.Logic
artık daha kolay test edilebilir. Artık Data
sahte verilerle doğrudan başlatabilir ve iletebilirsiniz. Alaylara veya karmaşık test iskelesine gerek yoktur.DataFromDependency
ki doğrudan referanslar, Dependency
gibi aynı modülde olduğunu Logic
, daha sonra Logic
modül hala doğrudan bağlıdır Dependency
derleme 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 Data
için, aynı modülde olmalı Logic
, ancak aynı modülde DataFromDependency
olmalıdır Dependency
.
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ı:
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.
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.
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;
}
}
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ı.
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/
Diyelim ki iki sınıfımız var: Engineer
ve 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 OCP
da 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 Interface
Programcı 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: DependencyInjection
yapmamızı yardımcı olabilir DIP
ve SRP
de.
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);
}
}
GoodEncoder
I / 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ı IReadable
ve IWriteable
hiç rahatsız etmeyecektir.
GoodEncoder
ikinci ö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.
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.