TypeScript İle SOLID 2
Selamlar;
Bugün ilk makalede bahsedilen SOLID prensiplerinin TypeScript üzerinde incelenmesine devam edeceğiz.
iskov ‘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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DAL { interface ILogin{ string DomainLogin(); string SocialLogin(); } public class PortalLogin : ILogin { public string DomainLogin() { return ""; } public string SocialLogin() { return ""; } } public class FacebookLogin : ILogin { public string DomainLogin() { return ""; } public string SocialLogin() { return ""; } } } |
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.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
abstract class Login { userName: string; password: string; constructor(LoginName: string, LoginPassword: string) { this.userName = LoginName; this.password = LoginPassword; } abstract DomainLogin() : string; } interface IFacebookLogin { FaceLogin(user: string, password: string): string; } class PortalLogin extends Login { constructor(LoginName: string, LoginPassword) { super(LoginName, LoginPassword); } DomainLogin(): string { if (this.userName == 'Bora' && this.password == "12345") { return "Wellcome Bora KAŞMER"; } else { return "Your Domain Username or Password Wrong!" } } } class SocialLogin extends Login implements IFacebookLogin { constructor(protected LoginName: string, protected LoginPassword) { super(LoginName, LoginPassword); } DomainLogin(): string { if (this.userName == 'Bora' && this.password == "12345") { return "Wellcome Bora KAŞMER"; } else { return "Your Domain Username or Password Wrong!" } } FaceLogin(fbUserName: string, password: string): string { if (fbUserName == 'FaceBora' && password == "654321") { return "Wellcome Facebook Borsoft"; } else { return "Your Facebook Credentials Wrong!" } } } var faceLogin = new SocialLogin("Bora", "1234"); alert(faceLogin.DomainLogin()); alert(faceLogin.FaceLogin("FaceBora", "654321")); |
Yukarıdaki kodun ekran çıktısı aşağıdaki gibidir:
nterface 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 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.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
interface IRobot { Calis(); Dur(); } interface ICalisan extends IRobot { YemekYe(); } class Robot implements IRobot { Calis() { alert("Robot Çalışıyor"); } Dur() { alert("Robot Duruyor"); } } class Isci implements ICalisan { public Calis() { alert("İşçi Çalışıyor"); } public Dur() { alert("İşçi Duruyor"); } public YemekYe() { alert("İşçi YemekYe yiyor"); } } var rbt = new Robot(); var isc = new Isci(); rbt.Calis(); rbt.Dur(); isc.Calis(); isc.Dur(); isc.YemekYe(); |
Yukarıdaki kodun ekran çıktısı aşağıdaki gibidir:
ependency 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.
Örnek DAL Modeli:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
namespace DAL { public class ManageAdvertisement { Banner bnr; Video vid; public ManageAdvertisement() { bnr = new Banner(); vid = new Video(); } public void Show() { bnr.ShowBanner(); vid.ShowVideo(); } } public class Banner { public void ShowBanner() { } } public class Video { public void ShowVideo() { } } } |
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.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
interface IAdvertisment { Show(); } class Banner implements IAdvertisment { public Show(): string { return "Banner Showed!"; } } class Video implements IAdvertisment { public Show(): string { return "Video Showed!"; } } class Advertisment { _advertisment: IAdvertisment; constructor(advertisment: IAdvertisment) { this._advertisment = advertisment; } public Show(): void { alert(this._advertisment.Show()); } } var AdvertismentBanner: Advertisment; var AdvertismentVideo: Advertisment; AdvertismentBanner = new Advertisment(new Banner()); AdvertismentBanner.Show(); AdvertismentVideo = new Advertisment(new Video()); AdvertismentVideo.Show(); |
Yukarıdaki kodların ekran çıktısı aşağıdaki gibidir:
Böylece SOLID prensiplerine TypeScript üzerinden kodlama yaparak irdeledik. Yeni bir makalede görüşmek üzere hoşçakalın.
Source:
Son Yorumlar