TypeScript İle SOLID 2

Selamlar;

Bugün ilk makalede bahsedilen SOLID prensiplerinin TypeScript üzerinde incelenmesine devam edeceğiz.

Liskov

Liskov ‘s Substitution Principle: Öncelikle sorundan bahsedelim. Yukarıda “FacebookLogin” ve “PortalLogin” sınıfları “ILogin” interface’inden türetilmiştir. Ama aslında “PortalLogin” sınıfının “SocialLogin()” adında bir metoda ihtiyacı yoktur. İşte tam bu durumda “ILogin” interface’inden türetme yapıldığı için türüyen sınıf içinde ilgili method ya boş bir şekilde yazılıp bırakılacaktır, ya da türeyen sınıf’da “if” bloğu içinde tür eğer “FacebookLogin” ise => “SocialLogin()” methodunu kullan şeklinde bir koşul bildirilecektir.

Bu yanlış bir tasarımın sonucudur. Yani LSP prensibine göre türetilen sınıflar, türeyen sınıfların tüm özelliklerini kullanmalıdır. Bu sınıfı kullanan herhangi biri, bu sınıfın alt sınıflarını kullanmak için ayrıca bir işlem yapmamalıdır. Üst sınıf ile alt sınıf arasında bir farklılık olmamalıdır. Çünkü alt sınıfta oluşturulan methodlar üst sınıf davranışını örnek alınarak yazılmışlardır.

Sorunu bulduğumuza göre artık çözüme geçebiliriz: Aşağıda görüldüğü gibi “Login” abstract sınıfında sadece “DomainLogin“‘ methodu tanımlanmıştır. “IFacebookLogin” interface’inde “FacebookLogin()” methodu tanımlanmıştır. “PortalLogin” sınıfı =>”Login” abstract sınıfından türetilmiş ve sadece “DomainLogin()“methodunu içermektedir.  “SocialLogin” sınıfı => “Login ve IFacebookLogin“‘den türetilmiş ve böylece hem “DomainLogin()” hem de “SocialLogin()” methodlarını içermektedir. Böylece LSP prensibine uyulmuştur.

LSP2

TypeScript Kodları: “Login” abstract sınıfı ve “IFacebookLogin” interface’i aşağıdaki gibi tanımlanmıştır. “PortalLogin” sınıfı “extends” keyword’ü ile “Login” abstract  sınıfından türetilmiş ve üst sınıftaki “DomainLogin()” methodu tanımlanmıştır. Ayrıca Constructor’ında “LoginName” ve “LoginPassword” parametreleri “super” keyword’ü ile türetildiği “Login” sınıfına gönderilmiştir. “SocialLogin” sınıfı hem “extends” keyword’ü ile “Login” sınıfından hem de “implements” keyword’ü ile “IFacebookLogin” interface’inden  türetilmişdir. Böylece hem “DomainLogin()” hem de “FaceLogin()” methodlarını içermekte ve  böylece LSP prensibi korunmaktadır.

Not: TypeScript’de abstract desteği TypeScript V 1.6 ve sonrası için yandaki linkde görüldüğü gibi gelmiştir. https://github.com/Microsoft/TypeScript/issues/3578. Buradaki örnek Visual Studio 2015 Update 1 ile birlikte gelen TypeScript V 1.7.2 ile yazılmıştır. “tsc -v” komutu ile ilgili versiyon aşağıdaki gibi öğrenilebilir.

TsVersion

Yukarıdaki kodun ekran çıktısı aşağıdaki gibidir:

screen

Interface Segregation Principle: Öncelikle karşımıza çıkabilecek ilgili sorunu hep beraber inceleyelim. Aşağıda “Isci” ve “Robot” sınıfı “ICalisan” interface’inden türetilmiştir. Ancak “Robot” sınıfının yemek yeme  özelliği olmadığı için :) “YemekYe()” methodu bu sınıfta bulunmamalıdır. Bu durumda arayüz ayırım prensibine göre, bir arayüze gerektiğinden fazla yetenek eklenmemelidir. Burada mimari bir yanlışlık ile “ICalisan” interface’ine fazla özellik verilmiştir.

Interface

Interface Segregation Prensibine göre “ICalisan” implemente olacağı tüm sınıflar için uygun değil ise ilgili interface bölünür. Yani ilgili arayüz ayrılır. Aşağıda görüldüğü gibi “ICalısan” bölünmüs ve öncelikle “IRobot” interface ile “Calis() ve Dur()” methodları “Robot” sınıfı için tanımlanmış daha sonra “ICalisan” interface’i de “IRobot” inteface’inden türetilerek var olan methodlara “YemekYe()” methodu da eklenip “Isci” sınıfına uygun hale getirilmiştir. Daha sonra Robot sınıfı =>”IRobot“‘dan ve Isci sınıfı =>”ICalisan“‘dan türetilmiştir. Böylece ilgili interface bölünerek her interface’in implemente olduğu sınıfa uygun hale gelmesi sağlanmıştır.

ISS

TypeScript Kodları: Aşağıda görüldüğü gibi “ICalisan” interface’i “extends” keywordü ile “IRobot” interface’inden türetilip “Isci” sınıfı için “implemente” edilmiştir. “ICalisan” interface’inin “IRobot” interface’inden farkı olarak, “YemekYe()” methodu eklenmesidir. Böylece “IRobot“‘dan türüyen “Robot” sınıfı sadece kendisi ile alkalı “Dur() ve Calis()” methodlarına sahip iken “Isci” sınıfına bunalara ek olarak “YemekYe()” özelliği kazandırılmıştır. Böylece var olan bir interface 2 ye ayrılarak, her biri türetildikleri sınıflara uygun hale getirilmiştir.

Yukarıdaki kodun ekran çıktısı aşağıdaki gibidir:

YemekYe

Dependency Inversion Principle: İsterseniz öncelikle hangi tipte soruna karşı uygulanabilecek bir prensip olduğunu inceleyelim. Aşağıda görüldüğü gibi “Banner” ve “Video” adında 2 sınıf vardır. “ManageAdvertisment” sınıfı “Show()” methodu içinde bu iki sınıfı da türeterek kullanmakta ve ilgili reklam modelini göstermektedir. İşte tam bu noktada “ManageAdvertisment” sınıfı alt sınıf olan “Banner” ve “Video” sınıflarına bağımlı hale getirilmiş ve böylece alt sınıflarda olabilecek bir değişikliğin üst sınıf olan “ManageAdvertisment“‘ı etkilemesine sebep olunulmuştur. Kısaca bağımlılığın ters çevirilmesi(DIP) ilkesine göre üst seviye sınıflar,  alt seviyeli sınıflara bağımlı olmamalıdır. Alt seviye sınıflarda yapılacak bir değişiklik üst seviye sınıfları etkilememelidir. Alt seviye sınıflarda veya modellerde değişiklik olma ihtimali her zaman çok yüksektir. İlgili değişikliğin en alt seviyeden en üst seviyeye kadar yönetilmesi bazen çok zor olabilir. Yine aynı şekilde en  tepeden en alta kadar tam bir bağımlılık olmasından dolayı ilgili sınıf veya modelin başka bir projede kullanılması istenildiğine, ilgili diğer tüm model ve sınıflarında dahil edilmesi gerekmektedir. Bu da hem maliyet hem de tekrar kullanılabilirliğe tamamen aykırı bir durumdur.

Dependecy

Örnek DAL Modeli:

Artık sorunu anladığımıza göre şimdi sıra geldi problemin çözümüne: Aşağıda görüldüğü gibi “Advertisment” sınıfının “Banner” ve “Video” sınıfına bağımlılığı “IAdvertisment” interface’i ile ortadan kaldırılmıştır. Yani ilgili alt sınıflarda oluşabilecek bir değişiklik “Advertisment” üst sınıfını etkilemeyecektir. Ayrıca “IAdvertisment” interface’ini implemente edecek “RichMedia” adlı yeni bir reklam modeli sisteme dahil edilmek istendiğinde, “Advertisment” sınıfı altında hiçbir değişiklik yapılmadan kolaylıkla eklenebilecektir.

DIP

TypeScript Kodları: Aşağıda görüldüğü gibi “Banner” ve “Video” sınıfı “implements” keyword’ü ile “IAdvertisment” interface’inden türetilmiştir. Böylece her iki sınıf için ortak olan “Show()” methodu tanımlanmıştır. “Advertisment” sınıfı constructor’ında “IAdvertisment” türünde bir nesne beklemektedir. Böylece üst sınıf ile alt sınıf arasındaki bağmlılık koparılmış ve araya ilgili interface koyulmuştur. “Advertisment” sınıfında “Show()” adlı bir method oluşturulmuş ve gönderilen sınıfın “Show()” methodu burada tetiklenmiştir. Böylece genişletilebilir ama değiştirilemez bir yapı oluşturulup üst sınıf ile alt sınıflar arasındaki bağımlılık kaldırılmıştır.

Yukarıdaki kodların ekran çıktısı aşağıdaki gibidir:

DIP2

Böylece SOLID prensiplerine TypeScript üzerinden kodlama yaparak irdeledik. Yeni bir makalede görüşmek üzere hoşçakalın.

Source Code: https://github.com/borakasmer/TypeScriptSOLID2

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir