SOLID – Yazılım Prensibleri
SOLID 5 tane kuralın baş harflerinden oluşmuş bir prensiptir. Nedir Bu kurallar ?
- Single Responsiblity Princible (Tek Sorumluluk Kuralı)
- Open-Closed Princible (Açıklık – Kapalılık Kuralı)
- Liskov’s Substitution Princible (Liskov’un Yer değiştirme Kuralı)
- Interface Segregation Princible (Interface Parçalama Kuralı)
- Dependecy Inversion Princible (Bağımlılıkları Tersine Çevirme Kuralı)
Yukardaki kurallar tek başlarına çok şey anlatmayabilirler lakin bir hedef ve kendimize koymuş olduğumuz bir şart ile uygulanırsa o zaman yazılım projemizde bakımı kolay, anlaşılır ve genişletilebilir bir yapıya sahip olabileceğiz. Bağımlılıklarımızı yönetebileceğiz ve birbirlerine louse coupling (gevşek bağlı) sınıflar üretebileceğiz. Kullanmadığımız taktirde ise projemiz büyüdükçe bir sürü sorunlar ile karşılaşıcağız ve proje kontrolümüzden çıkıcaktır.
İlgili bağlantılar üzerinden derslere erişim sağlayabilirsiniz.
Single Responsiblity Princible
Bu kural sınıflarımızın, metotlarımızın ya da modüllerimizin tek sorumluluğu olması gerektiğini söylüyor. Yazdığımız servis sınıfları, metotlar vs. yazılım birimlerinin tek sorumluluğu olmalıdır.
Single Responsibility Principle was defined by Robert C. Martin as –
→A class should have only one reason to change.
Örneğin çarpma ve bölme işlemlerini aynı fonksiyon içerisinde yapıyorsak bu fonksiyonu parçalayarak sadece çarpma işlemini ve sadece bölme işlemini yapan fonksiyonlar oluşturmalıyız. Bu durum kod tekrarını önleyerek kodun tekrar kullanılabilirliğini arttırır.
Kötü Bir Örnek :
İlk olarak kötü bir örnek olarak aşağıya baktığımızda hem customer için değişkenlerimiz (model) hem de dao işlemlerini içeren hem de login olmayı kapsayan bir yapı görmekteyiz. Burası SRP ‘ye uymuyor. Neden diyecek olursanız şartımız bizim class ‘ımız tek bir sorumluluk alması gerekiyor. Burada hem DAO işlemleri hemde model olmaya çalışıyor.
package SOLID.SRP.Bad;
public class Customer {
private int id;
private String userName;
private String email;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void create(){
}
public void delete(){
}
public void login(){
}
}İyi Bir Örnek
Customer:
package SOLID.SRP.Good;
public class Customer {
private int id;
private String userName;
private String email;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}CustomerDAOImp
package SOLID.SRP.Good;
public class CustomerDAOImp implements ICustomerDAO{
@Override
public void create() {
}
@Override
public void updare(Customer customer) {
}
@Override
public void delete(Customer customer) {
}
}ICustomerDAO
package SOLID.SRP.Good;
public interface ICustomerDAO {
void create();
void updare(Customer customer);
void delete(Customer customer);
}ILogin
package SOLID.SRP.Good;
public interface ILogin {
void login(Customer customer);
}LoginImp
package SOLID.SRP.Good;
public class LoginImp implements ILogin {
@Override
public void login(Customer customer) {
if(customer.getEmail().equals("umiitkose@gmail.com") && customer.getPassword().equals("1234")){
System.out.println("Şifreniz Doğru ");
}
else {
System.out.println("Şifreniz Yanlış..");
}
}
}Open Closed Princible
Tanınmış yazılım ustalarından Ivar Jacobson bu konuda şöyle bir açıklamada bulunmuştur:
“All systems change during their life cycles. This must be born in mind when developing systems are excepted to last longer than the first version.”
Şu şekilde tercüme edilebilir:
“Her program görev süresince değişikliğe uğrar. Bu ilk sürümden ötesi düşünülen programların yazılımında göz önünde bulundurulmalıdır.”
Bertrand Meyer tarafından geliştirilen bu prensip kısaca şöyle açıklanabilir:
“Programlar geliştirilmeye açık ama değiştirilmeye kapalı olmalıdır.”
Bu prensibi tek cümle ile kısaca anlatacak olursak sınıflarımız modüllerimiz ve fonksiyonlarımız geliştirmeye açık değişime kapalı olmalıdır.
Örneğin Spring Framework’ünü ele alırsak, bu frameworkün temel işlevlerini ve mantığını değiştiremeyiz fakat framework içerisindeki sınıfları kalıtım alarak yapıyı genişletebiliriz. Örneğin Spring Security’nin varsayılan oturum oluşturma ve oturum yönetme servislerini kullanabileceğimiz gibi bu sınıfları kalıtım alarak oturum oluşturma ve yönetme işlemlerini kendimizde yapabiliriz.
Başka bir örnek olarak ise gerçek hayatta müşterilerimizden hatta kendimizden örnek verelim. Bir projeye başladığınızda bir özelliğini kaç kere değiştirmişsinizdir ? Ya da tamamen biten bir projeniz var mıdır ? İşte bunun gerçekliğinin bilincinde olan bu prensip bu sorunla ilgili çözüm önerisi olarak çalışan kodun değiştirilmeyip genişleyebilir bir sistem olarak tasarlanması gerektiğini savunuyor.
Özcan Hocamızın sitesinden bu prensibi güzel bir şekilde yorumlayan yazısını okursak daha detaylı anlayabileceğiz.
Programı geliştirmek, programa yeni bir davranış biçimi eklemek anlamına gelmektedir. OCP ye göre programlar geliştirmeye açık olmalıdır, yani programı oluşturan modüller yeni davranış biçimlerini sergileyecek şekilde genişletilebilmelidirler. Bir modüle yeni bir davranış biçimi kazandırılarak düşünülen değişiklik sağlanır. Bu yeni kod yazılarak gerçekleştirilir ( bu yüzden bu işleme değiştirme değil, genişletme denir), mevcut kodu değiştirerek değil! Eğer kendinizi bir müşteri gereksinimini mevcut kod üzerinde değişiklik yaparken bulursanız, biliniz ki OCP prensibine ters düşüyorsunuz. Kod üzerinde yapılan değişiklik, bir sonraki gereksinimlerinde ayni şekilde implemente edilmesini zorunlu kılacaktır. Bu durum, kodun zaman içinde içinden çıkılmaz ve çok karmaşık bir yapıya dönüşmesini çabuklaştırır.
Şimdi Örneğimize geçersek;
Kötü bir örnek için :
AreaCalculator
package SOLID.OCP.Bad;
public class AreaCalculator {
public double Area(Object shape) {
double area = 0;
if (shape instanceof Rectangle) {
area = ((Rectangle) shape).getWidth() * ((Rectangle) shape).getHeight();
} else {
Circle circle = (Circle)shape;
area += circle.getRadius()* circle.getRadius() * Math.PI;
}
return area;
}
}Circle
package SOLID.OCP.Bad;
public class Circle {
private double Radius;
public double getRadius() {
return Radius;
}
public void setRadius(double radius) {
Radius = radius;
}
}Rectangle
package SOLID.OCP.Bad;
public class Rectangle {
private double width;
private double height;
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}İyi Bir Örnek İçin:
Shape:
package SOLID.OCP.Good;
public abstract class Shape
{
public abstract double Area();
}Rectangle
package SOLID.OCP.Good;
public class Rectangle extends Shape
{
private double width;
private double height;
@Override
public double Area() {
return width*height;
}
}Circle
package SOLID.OCP.Good;
public class Circle extends Shape {
private double radius;
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public double Area() {
return radius*radius*Math.PI;
}
}Liskov’s Substitution Princible
Barbara Liskov tarafından geliştirilmiş bu prensipte Hali hazırda MIT (Massachusetts Institute of Technology) programlama metodolojileri grup liderliği yapan bu dahi kadın bakın 1988 yılında Data Abstraction and Hierarchy adlı kitabında ne demiş?
“What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.”
Çevirisi ise aynı şekilde karmaşık 🙂
“Aranan yer değiştirme özelliği şöyle tanımlanabilir: T cinsinden parametre alan tüm programlar (fonksiyonlar) P olacak şekilde, S tipinde o1 nesnesi ve T tipinde o2 nesnesi olsun. Eğer o1 ile o2 nesneleri yer değiştirdiğinde P’nin davranışı değişmiyorsa S tipi T tipinin alt tipidir!”
Peki anlaşılır şekilde özetleyecek olursak bu prensibin amacı;
Alt sınıflardan oluşturulan nesneler üst sınıfların nesneleriyle yer değiştirdiklerinde aynı davranışı göstermek zorundadırlar.
OOP’nin temeli olan bu kural bize oluşturduğumuz alt sınıf nesnelerini üst sınıfların nesneleri ile yer değiştirirsek aynı davranışı sergilemeleri gerektiğini söylüyor.
LSP prensibi Open Closed prensibinin özel bir türüdür desek yanlış olmaz. OCP’de de olduğu gibi LSP de de genişlemeye açık yapılar söz konusudur. Her ne kadar anlaşılması biraz zor olsa da LSP ilk bakışta, altında yatan ana fikri: alt sınıflardan oluşan nesnelerin üst sınıfın nesneleri ile yer değiştirdikleri zaman, aynı davranışı sergilemesini beklemektir.
Örneğimizde ise;
Farklı veri kaynakları (SQL, Excel dosyası vs.) ile çalışabilecek bir uygulama geliştirelim. Tüm bu veri kaynakları ile “yükleme” ve “kayıt” işlemleri yapabileceğinizi analizini ettik ve bu metotları bir abstract sınıfta (ya da interface) tutmaya karar verdiniz. Sonuç olarak şöyle bir abstract sınıf geliştirmiş oldunuz:
Kötü bir Kod:
DataSource
package SOLID.LSP.Bad;
public abstract class DataSource {
public abstract void getAllData();
public abstract void Save();
}DatabaseSource
package SOLID.LSP.Bad;
public class DatabaseSource extends DataSource{
@Override
public void getAllData() {
}
@Override
public void Save() {
}
}ExcelSource
package SOLID.LSP.Bad;
public class ExcelSource extends DataSource {
@Override
public void getAllData() {
}
@Override
public void Save() {
}
}XmlSource
package SOLID.LSP.Bad;
import jdk.jshell.spi.ExecutionControl;
public class XmlSource extends DataSource {
@Override
public void getAllData() {
}
@Override
public void Save() {
try {
throw new ExecutionControl.NotImplementedException("Not Implemented");
} catch (ExecutionControl.NotImplementedException e) {
e.printStackTrace();
}
}
}LSP uygulanmış hali ise;
DataSource
package SOLID.LSP.Good;
public abstract class DataSource {
public abstract void getAllData();
}DatabaseSource
package SOLID.LSP.Good;
import SOLID.LSP.Good.DataSource;
public class DatabaseSource extends DataSource implements ISave{
@Override
public void getAllData() {
}
@Override
public void Save() {
}
}ExcelSource
package SOLID.LSP.Good;
import SOLID.LSP.Good.DataSource;
public class ExcelSource extends DataSource implements ISave{
@Override
public void getAllData() {
}
@Override
public void Save() {
}
}ISave
package SOLID.LSP.Good;
public interface ISave {
void Save();
}XmlSource
package SOLID.LSP.Good;
import SOLID.LSP.Good.DataSource;
public class XmlSource extends DataSource {
@Override
public void getAllData() {
}
}Interface Segregation Princible
Bu özellik tek sorumluluk kuralına benzer şeyler söylemektedir. İçinde her şeyi barındıran interfaceler yerine belli amaca hizmet eden interfaceler üretmeliyiz. Eğer interface parçalama işini iyi yaparsak gereksiz fonksiyonların sınıflar tarafından zorla uygulanmasını önlemiş oluruz.
Bu prensip bize kısaca şunu söylüyor; “Nesneler asla ihtiyacı olmayan property/metot vs içeren interface’leri implement etmeye zorlanmamalıdır !“.
Örneğimize geçersek;
Kısaca database ve File loglarının toplandığı bir kısım var. Database için connection açmamız ve kapamamız gerekiyor. File ‘da ise bu durum mevcut değil. 2sinin ortak tek yanı log metoduna sahip olunması. Bunun için 2 sınıf tek bir interface’i implemente etmeye zorlanmamalıdır. !
Yine Kötü Bir Örnek
ILog
package SOLID.ISP.Bad;
import jdk.jshell.spi.ExecutionControl;
public interface ILog {
void Log(String message);
void OpenConnection();
void CloseConnection();
}
FileLogger
package SOLID.ISP.Bad;
import jdk.jshell.spi.ExecutionControl;
public class FileLogger implements ILog {
@Override
public void Log(String message) {
}
@Override
public void OpenConnection() {
try {
throw new ExecutionControl.NotImplementedException("Not Implement");
} catch (ExecutionControl.NotImplementedException e) {
e.printStackTrace();
}
}
@Override
public void CloseConnection() {
try {
throw new ExecutionControl.NotImplementedException("Not Implement");
} catch (ExecutionControl.NotImplementedException e) {
e.printStackTrace();
}
}
/*
Bu 2 metod aslında File Logger için gerekli değil
@Override
public void OpenConnection() {
}
@Override
public void CloseConnection() {
}*/}DBLogger
package SOLID.ISP.Bad;
public class DBLogger implements ILog {
@Override
public void Log(String message) {
//Log bilgileri toplanır
}
@Override
public void OpenConnection() {
//bağlantı açılacağı kısım
}
@Override
public void CloseConnection() {
//bağlantı kapanacağı kısım
}
}İyi Bir Örnek :
ILog
package SOLID.ISP.Good;
public interface ILog {
void Log(String Message);
}IFileLog
package SOLID.ISP.Good;
public interface IFileLog extends ILog{
void CheckFileSize();
void GenerateFileName();
}IDBLog
package SOLID.ISP.Good;
public interface IDBLog extends ILog {
void OpenConnection();
void CloseConnection();
}FileLogger
package SOLID.ISP.Good;
public class FileLogger implements IFileLog {
@Override
public void CheckFileSize() {
}
@Override
public void GenerateFileName() {
}
@Override
public void Log(String Message) {
}
}
DbLogger
package SOLID.ISP.Good;
public class DbLogger implements IDBLog {
@Override
public void OpenConnection() {
}
@Override
public void CloseConnection() {
}
@Override
public void Log(String Message) {
}
}Dependency Inversion Principle
Bu kural bize bağımlılıkların soyut yapılara doğru olması gerektiğini söylüyor. Nesneleri direkt olarak birbirine sıkı sıkıya bağlamak yerine bağımlılıkları soyut sınıflara doğru yaparak nesnelerin birbirine gevşek bağlı(loose coupling) olması sağlanır.
Sıkça kullanılan IoC, Dependency Injection ve Dependency Inversion terimleri aralarında doğrudan ilişki vardır.
IoC (Inversion of Control): Kontrolün ters çevrilmesi, kontrolden vazgeçme gibi bir türkçesi vardır. Nesnelerin yaşam döngülerinin (oluşturulma, aktif olma, pasif olma vb.) bizim kontrolümüzden alınarak frameworklere devredilmesidir. Örneğin Spring Framework ile uygulama geliştirdiğimizde oluşturduğumuz beanlerin oluşturulması ve ya yok edilmesiyle biz ilgilenmiyoruz. Biz sadece o beani kullanarak bir metot çağırıyoruz.
Dependency Injection: Bağımlılıkları yükleme anlamına gelmektedir. Bu kavram Dependency Inversion ile doğrudan alakalıdır. Dependency Inversion bir prensiptir, Dependecy Injection ise bu prensibi gerçekleştirmenin bir yoludur.
Dependency Injection kullanmazsak muhtemelen kodlarımız şu şekilde olacaktır.
<span class="pl-k">class</span> <span class="pl-en">PDFWriter</span> {
<span class="pl-k">public</span> <span class="pl-k">void</span> <span class="pl-en">print</span>() {
<span class="pl-smi">System</span><span class="pl-k">.</span>out<span class="pl-k">.</span>println(<span class="pl-s"><span class="pl-pds">"</span>PDF Writer<span class="pl-pds">"</span></span>);
}
}
<span class="pl-k">class</span> <span class="pl-en">Writer</span> {
<span class="pl-k">public</span> <span class="pl-smi">PDFWriter</span> pdfWriter;
<span class="pl-en">Writer</span>() {
pdfWriter <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-smi">PDFWriter</span>();
}
}Burada Writer sınıfımız şuan için sadece PDF için çıktı veriyor. Çünkü PDFWriter sınıfına doğrudan bağlı. Bu bize genişletilebilir bir yapı sağlamaz. Biz PDF yerine artık Excel çıktısı kullanmak istesek, alt sınıflarda yaptığımız ilgili değişiklikler üst sınıfları da etkileyecek. Şu an için sadece etkilenen bir kaç sınıfımız var ama proje genişledikçe bu sayı yüzlere çıkabilir. Biz hepsi için bu değişikliği uygulamalıyız. Bunu önlemek için bağımlılıkları dışarıdan yükleyebiliriz. Bu yöntemin adı dependency injectiondır.
Bağımlılıkları 3 farklı yöntem ile yükleyebiliriz:
- Constructor injection: Bağımlılık yükleme işleminin kurucu fonksiyonda yapıldığı yöntem.
- Setter injection: Bağımlılık yükleme işleminin set fonksiyonlarında yapıldığı yöntem.
- Interface injection: Bağımlılıkların interfaceler yardımıyla yüklendiği yöntem.
Constructor Injection
Bağımlılıklarımızı kurucu fonksiyona gönderdiğimiz parametre yardımıyla yüklüyoruz. Bir önceki kodda bazı değişiklikler yaparak bir örnek verelim.
<span class="pl-k">interface</span> <span class="pl-en">IWriter</span> {
<span class="pl-k">void</span> <span class="pl-en">print</span>();
}
<span class="pl-k">class</span> <span class="pl-en">PDFWriter</span> <span class="pl-k">implements</span> <span class="pl-e">IWriter</span> {
<span class="pl-k">@Override</span>
<span class="pl-k">public</span> <span class="pl-k">void</span> <span class="pl-en">print</span>() {
<span class="pl-smi">System</span><span class="pl-k">.</span>out<span class="pl-k">.</span>println(<span class="pl-s"><span class="pl-pds">"</span>PDF Writer<span class="pl-pds">"</span></span>);
}
}
<span class="pl-k">class</span> <span class="pl-en">ExcelWriter</span> <span class="pl-k">implements</span> <span class="pl-e">IWriter</span> {
<span class="pl-k">@Override</span>
<span class="pl-k">public</span> <span class="pl-k">void</span> <span class="pl-en">print</span>() {
<span class="pl-smi">System</span><span class="pl-k">.</span>out<span class="pl-k">.</span>println(<span class="pl-s"><span class="pl-pds">"</span>Excel Writer<span class="pl-pds">"</span></span>);
}
}
<span class="pl-k">class</span> <span class="pl-en">Writer</span> {
<span class="pl-k">public</span> <span class="pl-smi">IWriter</span> iWriter;
<span class="pl-en">Writer</span>(<span class="pl-smi">IWriter</span> <span class="pl-v">iWriter</span>) {
<span class="pl-c1">this</span><span class="pl-k">.</span>iWriter <span class="pl-k">=</span> iWriter;
}
}IWriter interfacesini ekledik ve PDFWriter ve ExcelWriter sınıflarında bu interfaceyi implemente ettik. Writer sınıfında ise direkt olarak PDFWriter sınıfından nesne oluşturmak yerine, bağımlılıkları soyut birimlere doğru yaparak IWriter interfacesinden bir referans oluşturduk. OOP’nin polymorphism özelliği sayesinde kurucu fonksiyona PDFWriter nesnesi gönderirsek interface referansı o nesneyi işaret edecektir.
Alt sınıflarda ise Excel ya da PDF çıktısı almak için şunları yapmamız yeterlidir.
<span class="pl-smi">Writer</span> writer <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-smi">Writer</span>(<span class="pl-k">new</span> <span class="pl-smi">ExcelWriter</span>());ya da
<span class="pl-smi">Writer</span> writer <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-smi">Writer</span>(<span class="pl-k">new</span> <span class="pl-smi">PDFWriter</span>());Alt sınıflarda yaptığımız bu değişiklik üst sınıfları hiç bir şekilde etkilemiyor ve biz yüzlerce yerde değişiklik yapmaktan kurtuluyoruz. Eğer ilerde bizden birde Word çıktısı isteyecek olursa yapmamız gereken Word çıktısı verebilen bir sınıf oluşturup writer nesnemizin bağımlılığını Word sınıf nesnesi olarak yüklemektir.
<span class="pl-k">class</span> <span class="pl-en">WordWriter</span> <span class="pl-k">implements</span> <span class="pl-e">IWriter</span> {
<span class="pl-k">@Override</span>
<span class="pl-k">public</span> <span class="pl-k">void</span> <span class="pl-en">print</span>() {
<span class="pl-smi">System</span><span class="pl-k">.</span>out<span class="pl-k">.</span>println(<span class="pl-s"><span class="pl-pds">"</span>Word Writer<span class="pl-pds">"</span></span>);
}
}ve
<span class="pl-smi">Writer</span> writer <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-smi">Writer</span>(<span class="pl-k">new</span> <span class="pl-smi">WordWriter</span>());bu değişiklikleri yapmamız yeterlidir.
Setter Injection
Biraz önceki yapıda IWriter bağımlılığımızı kurucu metot yerine set metodunda yükleyeceğiz. Önceki yöntemden avantaj olarak her defasında yeni bir nesne oluşturma gereksinimi yerine bir nesne oluşturup set fonksiyonunu kullanarak değişik çıktılar üretmesi sağlanabilir.
<span class="pl-k">class</span> <span class="pl-en">Writer</span> {
<span class="pl-k">private</span> <span class="pl-smi">IWriter</span> iWriter;
<span class="pl-k">public</span> <span class="pl-smi">IWriter</span> <span class="pl-en">getiWriter</span>() {
<span class="pl-k">return</span> iWriter;
}
<span class="pl-k">public</span> <span class="pl-k">void</span> <span class="pl-en">setiWriter</span>(<span class="pl-smi">IWriter</span> <span class="pl-v">iWriter</span>) {
<span class="pl-c1">this</span><span class="pl-k">.</span>iWriter <span class="pl-k">=</span> iWriter;
}
}yükleme yapacağımız kısım ise şu şekilde olacaktır.
<span class="pl-smi">Writer</span> writer <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-smi">Writer</span>();
writer<span class="pl-k">.</span>setWriter(<span class="pl-k">new</span> <span class="pl-smi">PDFWriter</span>());Interface Injection
Bu yöntemde bağımlı nesneleri yüklemek için interfacelerden yardım alınır. Bağımlılıkları yüklemek için bir interface oluşturalım.
<span class="pl-k">public</span> <span class="pl-k">interface</span> <span class="pl-en">ServiceSetter</span> {
<span class="pl-k">public</span> <span class="pl-k">void</span> <span class="pl-en">setService</span>(<span class="pl-smi">Service</span> <span class="pl-v">service</span>);
}Artık bağımlılıkları bu arayüz yardımıyla yükleyeceğiz. Bu arada Service bir interface ve bu interfaceyi implemente eden değişik servis sınıfları vardır. Örneğin ListerService, FinderService vs.
<span class="pl-k">public</span> <span class="pl-k">class</span> <span class="pl-en">Client</span> <span class="pl-k">implements</span> <span class="pl-e">ServiceSetter</span> {
<span class="pl-k">private</span> <span class="pl-smi">Service</span> service;
<span class="pl-k">@Override</span>
<span class="pl-k">public</span> <span class="pl-k">void</span> <span class="pl-en">setService</span>(<span class="pl-smi">Service</span> <span class="pl-v">service</span>) {
<span class="pl-c1">this</span><span class="pl-k">.</span>service <span class="pl-k">=</span> service;
}
}yükleme yapacağımız kısım ise şöyle olmalıdır.
<span class="pl-smi">Service</span> service <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-smi">ListerService</span>();
<span class="pl-smi">Client</span> client <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-smi">Client</span>();
client<span class="pl-k">.</span>setService(service);
Tüm bu yöntemlerin amacı sınıfların birbirine sıkı bağlanmasını önlemektir. Bu sayede genişletilebilir yapılar kurularak, alt sınıf değişliklerinin üst sınıflara yansıması önlenmiş olur. Geliştiricileri arka plan işlerinden soyutlar ve geliştirici modülü genişletmek isterse ona arayüzler sunarak buna imkan verir.
Dependency Injection yani DI nedir ?
Martin Fowler tarafından ortaya atılan Bağımlılık enjeksiyonu inceliyeceğiz.
Buradaki amaç Bağımlılık oluşturacak parçaların ayrılıp, bunların dışardan verilmesi ile sistem içerisindeki bağımlılığı minumuma düşürmek.
Sınıf oluştururken new sözcüğünü kullanmayanınız var mı ? yani elbet kullanmıştır 😀
Bir sınıfta başka bir sınıfın nesnesini oluşturmak için kullandığınız new sözcüğünü unutmanızı, kullanmamanızı yani new kelimesini unutup silmenizi söyleyen yaklaşımdır. Gereken nesnenin ya Constructor’dan ya da Setter metoduyla parametre olarak alınmasının daha doğru olduğu söylenmiştir. Böylece iki sınıfın birbirine bağımlılığı azalacaktır.
Loosely Coupled adında bir ifade mevcuttur. Gevşek bağlılık diyoruz. Yani :
Elimizde “A” ve “B” sınıfı olsun. “A” sınıfında “B” nesnesi üretildiğini düşünün. Yazımızın devamında da örneklendireceğimiz gibi “A” sınıfı “B” sınıfına bağlı hale gelmiştir. Gün geldi “B” nesnesinde bir değişiklik olduğu zaman mecbur gidip “A” sınıfında da bu değişiklik üzerine çalışma yapmamız gerekecektir. Tamam, eğer burada konuştuğumuz gibi projeniz iki sınıftan ibaretse hiç Dependency Injection mevzusuna girmeden gidip “A” sınıfında değişikliği yapmanız mantıklıdır. Ama yüzlerce class’lardan oluşan bir proje için bunu denemenizi pek tavsiye etmem. Eee, peki ne yapacağız? diye sorarsanız eğer, “A” nın “B” ye olan bağımlılığını minumuma indirgeyeceğiz. Tabi bunun nasıl yapıldığını yazının devamında irdeleyeceğiz.
Dependency Injection çoğu zaman Dependency Inversion ile karıştırılır. Fakat Dependency Inversion problem çözmeye yarayan bir prensip iken Dependency Injection ise bu prensibi uygulayan bir Design Pattern’dir.
Dİ : Bağımlılıkları soyutlamaktır. İki teknik ile bu işlemi gerçekleştirelim.
Constructor Injection
Setter Injection
class Araba
{
public void GazVer()
{
//…
}
public void FrenYap()
{
//…
}
public void SagaSinyal()
{
//…
}
public void SolaSinyal()
{
//..
}
}
class Vasita
{
Araba araba;
public Vasita()
{
araba = new Araba();
}
public void Kullan()
{
araba.GazVer();
araba.SagaSinyal();
araba.FrenYap();
araba.SolaSinyal();
}
}
Yukarıdaki kod bloğunu incelerseniz eğer yazının başında da anlattığım gibi “Vasita” sınıfı “Araba” sınıfına bağlı bir vaziyet arz etmektedir. Yani ben ne zaman “Araba” sınıfında bir değişiklik yapsam gelip “Vasita” sınıfında da o değişikliğe göre çalışma gerçekleştirmem gerekebilir. Haliyle onlarca sınıf söz konusu olduğu durumlarda bu pek mümkün olmayacaktır
Şimdi düşünün ki, “Vasita” sınıfına “Araba” yerine “Otobus” classı vermek durumunda kalırsam eğer gelip burada ki komutları güncellemek zorunda kalacağım. Haliyle sabahtan beri bahsettiğim onlarca sınıf durumunda bağımlılık arz eden sınıflarda güncelleme yapmak hiç yazılımcı işi değildir.
İşte… “Araba sınıfı istediği kadar değişsin ama Vasita sınıfının bundan haberi olmasın. Haberi olmasın ki Vasita sınıfıyla uğraşmak zorunda kalmayayım” diyorsak eğer Dependency Injection(DI) tasarımını uygulayacağız.
Şimdi DI desenini uygulamak için bir interface tanımlayacağız. Tanımladığımız bu Interface’den kalıtım alan tüm sınıflar doğal olarak Interface’in kalıbını, şartını sağlayacaktır.
interface ITasit
{
void GazVer();
void FrenYap();
void SagaSinyal();
void SolaSinyal();}
Evet, gördüğünüz gibi Interface’imizi oluşturduk. Şimdi “Araba”, “Otobus”, “Motor” vs. gibi taşıtlarımızı bu Interface’den türeteceğiz.
class Araba : ITasit
{
public void GazVer()
{
//…
}
public void FrenYap()
{
//…
}
public void SagaSinyal()
{
//…
}
public void SolaSinyal()
{
//..
}
}
class Otobus : ITasit
{
public void GazVer()
{
//…
}
public void FrenYap()
{
//…
}
public void SagaSinyal()
{
//…
}
public void SolaSinyal()
{
//..
}
}
class Motor : ITasit
{
public void GazVer()
{
//…
}
public void FrenYap()
{
//…
}
public void SagaSinyal()
{
//…
}
public void SolaSinyal()
{
//..
}
}
Şimdi vakit “Vasita” sınıfımıza DI desenini uygulamaya geldi.
class Vasita
{
ITasit _tasit;
public Vasita(ITasit tasit)
{
_tasit = tasit;
}
public void Kullan()
{
_tasit.GazVer();
_tasit.SagaSinyal();
_tasit.FrenYap();
_tasit.SolaSinyal();
}
}
Yukarıdaki kod bloğunu incelerseniz eğer, “ITasit” Interface’inden kalıtım alan herhangi bir sınıf “Vasita” sınıfına bir taşıt olabilir. Haliyle siz proje sürecinde “Vasita” sınıfına ister “Araba”, isterseniz “Otobus” yahut “Motor” verebilirsiniz. Nede olsa hepsi aynı metod ve işlevleri barındırdığı için hangi nesneyi verirseniz verin Interface referansı üzerinden ilgili nesne çalıştırılacaktır.
Olaya “Vasita” sınıfı açısından bakarsak eğer, kendisine verilen nesnenin ne olduğuyla ilgilenmemekte ve bağlı olduğu bir sınıf bulunmamaktadır. Yani olay araba, otobüs veyahut motor da olsa bu elemanların çalışma ve işleyişiyle ilgilenmemektedir. Nihayetinde araba ve otobüste gaz vermek için pedala basılırken, motorda sağ kolu çevirmek lazımdır. İşte Dependency Injection sayesinde “Vasita” sınıfı bu işlevlerin nasıl yapıldığıyla ilgilenmemekte, sadece ve sadece gelen araç hangisi olursa olsun gaz vermekte, frene basmakta ve sağa sola sinyal çakmaktadır. Eğer DI uygulanmazsa, “Vasita” gaz verme işleminde araba ve otobüs için pedalla, motor içinse sağ kolla ilgilenecektir.
Anlayacağınız “Vasita” sınıfı Dependency Injection sayesinde üzümü yiyor bağını sormuyor, farklı bir sınıfa olan bağını minimize etmiş oluyor.
Bu tasarım neticesinde aşağıdaki şekilde kullanım gerçekleştirilebilir.
class Program
{
static void Main(string[] args)
{
Vasita vasitaAraba = new Vasita(new Araba());
vasitaAraba.Kullan();
//veya
Vasita vasitaOtobus = new Vasita(new Otobus());
vasitaOtobus.Kullan();
//veya
Vasita vasitaMotor = new Vasita(new Motor());
vasitaMotor.Kullan();
}
}
Şuana kadar sizlere Dependency Injection’ı Constructor Injection(Constructor Based Dependecy Injection) yöntemi ile göstermiş oldum. Setter Injection(Setter Based Dependency Injection) yöntemi ile DI’yı uygulamak istiyorsanız eğer sadece “Vasita” sınıfında(yani projenizde DI’yı uyguladığınız sınıfta) aşağıdaki değişikliği yapmanız yeterlidir.
class Vasita
{
public ITasit _tasit { get; set; }
public void Kullan()
{
_tasit.GazVer();
_tasit.SagaSinyal();
_tasit.FrenYap();
_tasit.SolaSinyal();
}
}
class Program
{
static void Main(string[] args)
{
Vasita vasitaAraba = new Vasita();
vasitaAraba._tasit = new Araba();
vasitaAraba.Kullan();
//veya
Vasita vasitaOtobus = new Vasita();
vasitaOtobus._tasit = new Otobus();
vasitaOtobus.Kullan();
//veya
Vasita vasitaMotor = new Vasita();
vasitaMotor._tasit = new Motor();
vasitaMotor.Kullan();
}
}
Gördüğünüz gibi Dependency Injection kodunuzu özgürleştirmekte, sınıflar arasındaki bağı minimize etmektedir. Sınıflar çalıştırılırken üzümü yiyen amma bağını sormayan olgunlukta olurlar. Ama bunun yanında Visual Studio’nun “Go To Definiton” özelliğinide boşa çıkaran bir yapı olduğunu söyleyebilirim. Çünkü DI uyguladığınız sınıfta ilgili Interface’e “Go To Definiton” dediğiniz zaman sizi Interface’e gönderecektir. Eee haliyle hangi sınıfa bu Interface’in uygulandığını bulabilmeniz bazen işgenceye dönebilir. Hele hele bir başkası tarafından tasarlanmış proje üzerinde çalışma gerçekleştiriyorsanız….
Kısaca :
S.O.L.I.D Principles
- Single responsibility principle – A class should have only one reason to change.
- Open/Closed principle – Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
- Liskov Substitution Principle – Child classes should never break the parent class type definitions.
- Interface Segregation Principle – No client should be forced to depend on methods it does not use.
- Dependency inversion principle – High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.
Hepinize iyi çalışmalar dilerim…
Kaynakça:
Özcan Acar hocamızın blogunda SOLID ayrıntılı şekilde anlatılmaktadır.




