Welcome to My Blog 👋

Java, Spring Framework, Microservices, Docker, Kubernetes, AWS and Others 🚀
Follow Me

Bir android proje yarattığımız zaman Android Studio bizim için dosya ve klasör yapısını otomatik olarak oluşturur. Bu dosya ve klasörlerin her birinin bir anlamı ve görevi vardır.

Manifests

Uygulamaya ait konfigürasyonların belirlendiği dosyaları içeren dizindir.

Java

Uygulamaya ait Java ile yazılmış kaynak kodların bulunması gereken dizindir.

Res

Uygulamada kullanılan kaynakların bulunduğu dizindir. Bu kaynaklar derlenmeyen ancak uygulama içinde kullanılan dosyalardır. Örneğin ses, görsel gibi dosyalar bu dizinde bulunur. Bu dizinin içerisinde türlere özel alt dizinler bulunur.
  • Drawable : Görseller için kullanılır.
  • Raw : Medya dosyaları (ses, video) için kullanılır. 
  • Values : Genel değerler için kullanılır. Stringler, boyutlar, stiller, renkler vs.
  • Menu : Menü dosyaları için kullanılır.
  • Layout : Ekranlarımızın tasarım dosyaları için kullanılır.
  • Anim : Animasyon dosyaları için kullanılır.



Nesneye dayalı programlamada gerçek dünya nesnelerinin yazılıma taşınması amaçlanır. Gerçek nesneler birbirini kullanan, birbirine bağımlı olan varlıklardır. Gerçek hayattan kumanda ve televizyon gibi bir örnek verilebilir. Gerçek dünya nesnelerini yazılıma taşıdığımızda da nesnelerimizin arasında bir bağ yani bağımlılık olduğunu görebiliriz. Bu durum yazılım sistemimizde istemediğimiz bir durumdur çünkü mesela televizyonda yaptığımız bir değişiklik kumandayı da etkileyeceğinden fazladan kod düzenlemesi ve test ihtiyacı doğurur. Bu yüzden solid prensiplerinden biri olan açıklık kapalılık prensibi kodun değişime kapalı ama genişletmeye açık olduğunu söyler. Sistemimizde bir değişiklik yapma ihtiyacı duyduğumuzda kodumuzu değiştirmemeli, yeni kodlar eklemeliyiz. Bu şekilde birbirine bağımlı olan parçaları daha az etkiler, problemleri en aza indiririz. Bunu yapmak için nesneye yönelimli programlamanın temellerinden olan polimorfizm'i kullanarak soyut bir şekilde kod yazmalıyız. Bunu yaptığımızda tek başına problemimizi çözmez. Gerekli unsurlardan biri de Dependency Injection'dur. Bu yöntem ile bağımlılıklarımızı dışarıdan enjekte edebiliriz ve bağımlılıklar konusunda esneklik kazanabiliriz. 
Örneğin bir yazdır sınıfımızın olduğunu düşünelim. Yazdırma işlemi bir yazıcıya da olabilir bir excel'e de olabilir bir pdf'e de olabilir. Kullanıcının ilk etapta bir excel dosyasına çıktı almak istediğini düşünelim. Belli bir süre sonra değişen ihtiyaçlar doğrultusunda kullanıcı bir de pdf dosyasına çıktı almak isteyebilir. Bu durumda bizim kodda herhangi bir değişiklik yapmadan sadece pdf çıktısı almak için gerekli kodları ekleyerek güncellememizi yapmamız gerekir. Burada işlemin pdf ile mi yoksa excel ile mi gerçekleşeceğini de kodda belirlemek ve somut bir bağımlılık yaratmak yerine dışarıdan enjekte ederek soyut bir şekilde belirlemeliyiz.

Dependency Injection'un Temel Amaçları
  • Bağımlılıkların somut bir şekilde belirlenmesi yerine dışarıdan soyut bir şekilde belirlenmesi
  • Kaynak kodda değişikliğe ve yeniden derlemeye ihtiyaç duyulmaması
  • Bakımın ve güncellemelerin daha kolay yapılabilmesi
  • Bağımlılıkların tek bir noktada tasarlanması ve yönetilmesi
  • Bağımlılıklardan dolayı oluşan hataların en aza indirilmesi



interface IYazdir {
    
    public void yazdir();
}

class Excel implements IYazdir {
    
    public void yazdir() {
        System.out.println("Excel ile yazdırıldı!");
    }
}

class PDF implements IYazdir {
    
    public void yazdir() {
        System.out.println("PDF ile yazdırıldı!");
    }
}

class AySonuRaporu{
    
    IYazdir yazdirma;
    
    public AySonuRaporu(IYazdir yazdirma) {
        this.yazdirma = yazdirma;
    }
    
    public void aySonuRaporuCiktiAl() {
        yazdirma.yazdir();
    }
}

public class Test{

     public static void main(String []args) throws Exception{
         
        //Configurasyon dosyasından belirlediğimiz tür dışarıdan alınır
        //Alınan tür nesneye enjekte edilir
        //Burada configurasyon dosyasından okuma işlemleri yapılabilir
        //Ancak bu işin kalıplaşmış frameworkleri vardır. Örn spring
        //Spring burada bizim için configurasyonda belirttiğimiz türü enjekte eder
        //Konfigurasyon dosyasında türü pdf veya excel olarak değiştirerek kodda herhangi bir değişiklik yapmadan güncelleme yapabiliriz.
        //Örneğin bir word tipi eklendiğinde Word sınıfı yazılır ve konfigurasyon dosyasında tip word seçilir
        //Hiç bir kod değişikliği yapılmadığı için çok az test ihtiyacı doğar
        AySonuRaporu aySonuRaporu = new AySonuRaporu(new PDF());
        aySonuRaporu.aySonuRaporuCiktiAl();
     }
}

PDF ile yazdırıldı!

Günlük hayatta ve yazılım sistemlerinde genellikle varlıklar alt varlıklardan oluşur. Örneğin bir ülke içinde illeri onlar ilçeleri onlar da köyleri barındırırlar. Ülkede yaşanan herhangi bir gelişme onun içerisinde bulunduğu diğer alt varlıkları da etkiler. Bu durum günlük hayatta olduğu gibi yazılım sistemlerinde de vardır. Örneğin bir masaüstü uygulamada window açtığınızda bu window'un içinde resim ve yazılar olmaktadır. Bunlar da image ve label view'ları gibi alt varlıklar olabilirler. Window'un boyutunu değiştirdiğimiz zaman içinde bulunduğu yazı ve resimlerinde aynı şekilde boyutunun değiştiğini gözlemleyebiliriz. Bu problem yazılım sistemlerinde sıklıkla karşılaşılan bir problemdir. İç içe bulunan varlıkların tümü için tek tek yapılan değişikliğin yansıtılması yanlış bir yöntemdir ve işin içinden çıkılamayacak problemler ortaya çıkarır. Bu problemlerin çözümü için sunulan kalıplaşmış yöntem Composite Pattern'ıdır. Bu patern ile iç içe bulunan nesnelerin tümüne istenilen değişiklik tek bir noktadan uygulanır.

Uygulama
  • Component arayüzü iç içe bulunacak varlıklar için bir arayüz oluşturur ve operation metodu ortak işlemler için kullanılacak metotları temsilen yazılmıştır. Örneğimizde genel ön yüz elemanlarının arayüzüdür.
  • Composite sınıfı içerisine alt varlıklar alabilecek sınıfları temsil eder. Örneğimizde window sınıfıdır.
  • Leaf sınıfı içerisine bir alt varlık eklenemeyecek olan sınıfları temsil eder. Örneğimizde image sınıfıdır.


import java.util.ArrayList;

abstract class Component {
    protected String name;
    protected Boolean isParent;
    protected Component parent;
    protected ArrayList children;
    
    protected Component(String name, Boolean isParent) {
        this.name = name;
        this.isParent = isParent;
        if(isParent) {
            children = new ArrayList<>();
        }
    }
    
    public void add(Component component) throws Exception {
        if(isParent) {
            children.add(component);
        } else {
            throw new Exception("Bu işlem desteklenmiyor");
        }
    }
    
    public void remove(Component component) throws Exception {
        if(isParent) {
            children.remove(component);
        } else {
            throw new Exception("Bu işlem desteklenmiyor");
        }
    }
    
    public abstract void resize();
}

class Panel extends Component {
    
    public Panel(String name) {
        super(name, Boolean.TRUE);
    }
    
    public void resize() {
        System.out.println(name + " panelinin boyutu değişti!");
        if(!children.isEmpty()) {
            for(Component component : children) {
                component.resize();
            }
        }
    }
}

class ImageView extends Component {
    
    public ImageView(String name) {
        super(name, Boolean.FALSE);
    }
    
    public void resize() {
        System.out.println(name + " resminin boyutu değişti!");
    }
}

public class HelloWorld{

     public static void main(String []args) throws Exception{
        Panel panel = new Panel("Panel1");
        ImageView image1 = new ImageView("Image1");
        ImageView image2 = new ImageView("Image2");
        panel.add(image1);
        panel.add(image2);
        panel.resize();
     }
}

Panel1 panelinin boyutu değişti!
Image1 resminin boyutu değişti!
Image2 resminin boyutu değişti!

Facade Design Pattern'ının temel amacı soyutlama yapmaktır. Nesneye yönelimli programlamanın temellerinden biri soyutlamadır ve nesneye yönelimli programlamada daima kullanılır. Facade patern'ı sistemleri veya katmanları soyutlayarak bunların karmaşıklığını gizler, bir arayüz oluşturur, daha sonra kodların değiştirilebilirliğini sağlar ve doğrudan erişilmemesi gereken alanların kontrollü bir şekilde kullanılmasını sağlar. Örneğin yazılım sistemimizde veritabanı katmanını facade patern'ı ile soyutlayarak kodumuzu veritabanı sisteminden bağımsız yazabiliriz. Bir başka örnek olarak kullanılan kütüphaneler facade patern'ı ile bir arayüz şeklinde kullanıma açılarak o kütüphaneyi kullanacak kullanıcıya kolaylık ve doğru bir yol sağlanabilir. Bu verdiğim örnekler sıkça kullanılan ve genellikle karşınıza çıkacak örneklerdir.
Facade patern'ının en çok karşımıza çıktığı bir başka yer de framework'lerdir. Framework'lerin altında çalışan kütüphanelere veya alt sistemlere genellikle o framework'ün context sınıfı ile erişiriz. Bu context sınıfları genellikle facade patern'ı ile oluşturulur. Android'teki Context sınıfı veya Spring'teki ApplicationContext sınıfı örnek olarak verilebilir.

Uygulama
  • Facade sınıfı kullanacağı alt birimleri kendi içerisinde barındırır.
  • Facade sınıfı genellikle singleton pattern'ı ile tanımlanır.
  • Facade sınıfının alt birimleri factory metotlar ile dışarıya açılabilir.



class Facade {
    
    private AltSistem1 altSistem1;
    private AltSistem2 altSistem2;
    private static Facade facade;
    
    private Facade() {
        altSistem1 = new AltSistem1();
        altSistem2 = new AltSistem2();
    }
    
    public static Facade getFacade() {
        if(facade == null) {
            facade = new Facade();
        }
        return facade;
    }
    
    public void altSistem1IslemleriYap() {
        altSistem1.islem1();
        altSistem1.islem2();
    }
    
    public void altSistem2Islem1Yap() {
        altSistem2.islem1();
    }
}

class AltSistem1 {
    
    public void islem1() {
        System.out.println("Alt Sistem 1 İslem 1 yapıldı");
    }
    
    public void islem2() {
        System.out.println("Alt Sistem 1 İslem 2 yapıldı");
    }
}

class AltSistem2 {
    
    public void islem1() {
        System.out.println("Alt Sistem 2 İslem 1 yapıldı");
    }
}

public class Test {

     public static void main(String []args){
        Facade facade = Facade.getFacade();
        facade.altSistem1IslemleriYap();
        facade.altSistem2Islem1Yap();
     }
}

Alt Sistem 1 İslem 1 yapıldı
Alt Sistem 1 İslem 2 yapıldı
Alt Sistem 2 İslem 1 yapıldı

Adapter Design Pattern'ı adından da anlaşılacağı gibi iki uyumsuz nesnenin uyumlu bir şekilde çalışmasını sağlar ve aradaki uyumsuzluğu çözer.  Bu iki nesne farklı sistemlerin nesnesi de olabilir, aynı sistemdeki farklı çalışan iki nesne de olabilir. Örneğin elimizde bir model nesnesi olduğunu düşünelim. Bu model nesnesi kişilerin bilgilerini tuttuğu model olabilir. Bu kişileri bir liste halinde göstermek istersek listenin anlayacağı şekilde bu nesnedeki bilgileri düzenlememiz gerekir. Bu problemi adapter nesnesi çözer ve aldığı model sınıfını listenin anlayacağı şekilde adapte ederek uygun nesneyi oluşturur.

Uygulama
  • Adapter arayüzü ve bu arayüzü implement eden adapter sınıflarını tanımlarız. Bu adapter sınıfları iki sınıf arasındaki farklılıkları çözen sınıflardır. İhtiyacımız doğrultusunda istediğimiz kadar tanımlarız.
  • Adapter sınıfları içinde adapte edecekleri nesneleri barındırırlar.
  • Client sınıfları adapter arayüzü ile iki nesneyi adapte edebilirler.




import java.util.ArrayList;
import java.util.List;

class Insan {
    String ad;
    String soyad;
    
    public Insan(String ad, String soyad) {
        this.ad = ad;
        this.soyad = soyad;
    }
    
    public String getAd() {
        return ad;
    }
    
    public String getSoyad() {
        return soyad;
    }
}

class ListView {
    List<ListViewModel> listViewModelList;
    
    public ListView(List<ListViewModel> listViewModelList) {
        this.listViewModelList = listViewModelList;
    }
    
    public void goster() {
        for(ListViewModel model : listViewModelList) {
            System.out.println("Baslik : " + model.getBaslik());
            System.out.println("Aciklama : " + model.getAciklama());
        }
    }
}

class ListViewModel {
    String baslik;
    String aciklama;
    
    public ListViewModel(String baslik, String aciklama) {
        this.baslik = baslik;
        this.aciklama = aciklama;
    }
    
    public String getBaslik() {
        return baslik;
    }
    
    public String getAciklama() {
        return aciklama;
    }
}

interface Adapter {
    public void get();
}

class ListViewInsanAdapter implements Adapter {
    List<Insan> insanList;
    
    public ListViewInsanAdapter(List<Insan> insanList) {
        this.insanList = insanList;
    }
    
    public List<ListViewModel> get() {
        List<ListViewModel> listViewModelList = new ArrayList<>();
        for(Insan insan : insanList) {
            ListViewModel model = new ListViewModel(insan.getAd(), insan.getSoyad());
            listViewModelList.add(model);
        }
        return listViewModelList;
    }
}

public class Test {

     public static void main(String []args){
        Insan insan = new Insan("Berkay","Demirel");
        Insan insan2 = new Insan("Deneme","Insan");
        List<Insan> insanList = new ArrayList<>();
        insanList.add(insan);
        insanList.add(insan2);
        Adapter adapter = new ListViewInsanAdapter(insanList);
        ListView listView = new ListView(adapter.get());
        listView.goster();
     }
}

Baslik : Berkay
Aciklama : Demirel
Baslik : Deneme
Aciklama : Insan

Yazılım sistemimizi ihtiyaçlarımız doğrultusunda sınıflara bölerek tasarlarız ve bu sınıflara istediğimiz özellikleri veririz. Ancak bazı durumlarda sınıflardan ürettiğimiz nesnelerin farklı davranışlar sergilemesini isteyebiliriz. Bu probleme çözüm getiren kalıp Decorator Pattern'ıdır. Burada dikkat edilmesi gereken usun bu işlem çalışma zamanında yapılır ve sınıfta bir değişiklik yapılmaz. Sadece istenilen nesne üzerinde değişiklik yapılır ve o sınıftan üretilmiş diğer nesneler bu değişiklikten etkilenmezler. Örnek verecek olursak bir şekil arayüzümüzün olduğunu ve bu arayüzü kare, daire gibi sınıfların implement ettiğini düşünelim. Eğer kullanıcının seçtiği bir nesnenin renklendirilmesini istiyorsak o zaman sadece o nesneye dışarıdan ek özellik olarak bu durumu ekleyerek çizdirdiği şekli renkli hale getirebiliriz.

Uygulama
  • Component sınıfı ek özellik ekleyeceğimiz sınıfın arayüzü olmalıdır. Örneğimizde bu şekil arayüzüne denk gelmektedir.
  • ConcreteComponent sınıfı component arayüzünü implement eden ek özellik ekleyeceğimiz sınıflardır. Örneğimizde kare, daire gibi sınıflara denk gelmektedir.
  • Decorator sınıfımız bu patern'daki işlemleri yerine getirecek saf sanal sınıfımızdır. Bu sınıf ek özellik ekleyeceğimiz sınıfların arayüzünü implement etmeli ve ek özellik eklenecek nesneleri içermelidir.
  • ConcreteDecorator sınıfımız ek özellik ekleyeceğimiz nesnemize ekleyeceğimiz özellikleri belirttiğimiz sınıfımızdır.




interface Sekil {
    
    public void ciz();
}

class Kare implements Sekil {
    
    public void ciz() {
        System.out.println("Kare çizildi");
    }
}

class Daire implements Sekil {
    
    public void ciz() {
        System.out.println("Daire çizildi");
    }
}

abstract class SekilDecorator implements Sekil {
    protected Sekil decoratedSekil;
    
    public SekilDecorator(Sekil decoratedSekil) {
        super();
        this.decoratedSekil = decoratedSekil;
    }
} 

class BoyaDecorator extends SekilDecorator {
    protected String renk;
    
    public BoyaDecorator(Sekil decoratedSekil, String renk) {
        super(decoratedSekil);
        this.renk = renk;
    }
    
    public void ciz() {
        System.out.println(renk + " boyalı");
        decoratedSekil.ciz();
    }
}

public class Test {

     public static void main(String []args){
        Sekil kare = new Kare();
        kare.ciz();
        Sekil decoratedKare = new BoyaDecorator(new Kare(), "kırmızı");
        decoratedKare.ciz();
     }
}

Kare çizildi
kırmızı boyalı
Kare çizildi

Bilgisayar sistemlerinde geçmişi saklamak bir zorunluluktur. Örneğin kullanıcı işlemlerini yaparken muhtemel olarak bir hata yapacaktır ve yaptığı hatayı geri almak isteyecektir. Yazılım sistemimizde bu olaya çözüm bulmak için nesnelerimizin geçmişini saklamak zorunda kalabiliriz. Bu duruma çözüm getiren kalıp Memento Pattern'ıdır. Bu patern ile nesnemizin bir veya daha fazla geçmiş durumunu saklayarak istenilen zamanlarda bu geçmiş durumları nesneye tekrar yükleyebiliriz.

Uygulama
  • Bu yöntemi kullanacak client sınıflara bir arayüz görevi görecek Originator sınıfımızı yaratırız. Bu sınıf memento nesnelerinin yani geçmiş durumu tutacak nesneleri yönetmekten sorumludur.
  • Nesnemizin geçmiş durumunu saklaması için Memento sınıfını oluştururuz.
  • Birden fazla geçmiş saklamak istiyorsak Caretaker sınıfını oluştururuz. Saklayacağımız memento nesnelerini bu sınıf barındırır.



public class Memento {
     public String durum;
}

public class Originator {
     public String durum;
     
     public Memento save() {
         return new Memento(durum);
     }
     
     public String get(Memento memento) {
         return memento.getDurum();
     }
}

public class Caretaker {
    
    final Deque<EmpMemento> mementos = new ArrayDeque<>();
    public Memento getMemento()
    {
        return mementos.pop();
    }
    public void setMemento(Memento memento)
    {
        mementos.push(memento);
    }
}

public class TEST {
    
    public static void main() {
        Originator originator = new Originator("İlk durumum");
        Caretaker caretaker = new Caretaker();
        caretaker.setMemento(originator.save());
        originator.setDurum("İkinci durumum");
        caretaker.setMemento(originator.save());
        String sonDurum = originator.get(caretaker.getMemento);
    }
}

Eğer elimizde yorumlanması gereken bir ifade var ise bu iş için tanımlanmış kalıp Interpreter Design Pattern'ıdır. Bu patern ile yorumlanması gereken ifadeler birden farklı yöntem ile yorumlanması gerekebilir. Örneğin elimizde xml ve json formatında dosyalar olduğunu düşünelim. Bu dosyaların metin olarak ele alınıp uygun formata göre işlenmesi ve anlamlandırılması gerekmektedir. Bu kalıp ile bu probleme çözüm sunulmuştur.

Uygulama
  • Context sınıfı bize içeriği yani yorumlanması gereken ifadeyi sağlayan sınıftır.
  • Client sınıfı içeriği yorumlayarak işlem yapmak isteyen sınıftır.
  • AbstractExpression sınıfı ifadeyi yorumlamak için client sınıflarına sağlanan bir arayüzdür. İfadeyi yorumlayacak sınıflar bu arayüzü implement ederek yöntemlerini uygularlar.
  • TerminalExpression ile NonterminalExpression sınıfları arasındaki fark örneğin bir xml elementini yorumlarken bir tag içerisinde bir değer var ise o değeri alıp geriye dönebiliriz. Bu ifade bir terminalExpression'dır. Ancak eğer bir tag içinde bir veya daha fazla tag varsa o tag'in içindeki ifade tekrar yorumlanmak üzere nonterminalExpression sınıfına gönderilmelidir. Bu şekilde içerisindeki değerler döndürülünceye kadar ifade işlenmelidir.



public interface XMLAbstractExpression {

     public String interpret(Context context);
}

public class XmlTerminalExpression implements XMLAbstractExpression {
    
    public String interpret(Context context) {
        //Eğer tag'in içerisinde başka bir tag yoksa ifadeyi döndür
        //Eğer tag'in içerisinde başka bir tag varsa XmlNonterminalExpression'a gönder
    }
}

public class XmlNonterminalExpression implements XMLAbstractExpression {
    
    public String interpret(Context context) {
        //Eğer tag'in içerisinde başka bir tag yoksa ifadeyi XmlTerminalExpression'a gönder
        //Eğer tag'in içerisinde başka bir tag varsa ifadeyi tekrar XmlNonterminalExpression'a gönder
    }
}

public class Context {
    String expression;
}

public class TEST {
    
    public static void main() {
        XMLAbstractExpression xmlExpressionManager = new XmlTerminalExpression();
        Context context = new Context();
        context.setExpression("<note>
                                <to>Tove</to>
                                <from>Jani</from>
                                <heading>Reminder</heading>
                                <body>Don't forget me this weekend!</body>
                                </note>");
        xmlExpressionManager.interpret(context);
    }
}

Yazılımımızda aynı işi duruma göre farklı şekilde yapmak istersek kullanabileceğimiz yöntemlerden biri Template Method Pattern'ıdır. Bu patern uygulamamızda yapılacak bir işin çeşitli nedenlerden dolayı iki ya da daha fazla farklı yöntem ile yapılması gerekiyor ise bize uygun kalıbı sağlar. Örneğin elimizdeki bir metni farklı formatlarda göstermek isteyebiliriz. Kullanıcı pdf veya word dosyası olarak bu metni görüntülemek veya dışarı aktarmak isteyebilir. Bu durumda bu işleri birbirinden bağımsız şekillerde tasarlamak ileride problemlere yol açacaktır. Bu yüzden bu yöntemlerin tümünü aynı çatı altında tasarlayarak bir arayüz vasıtasıyla bu yöntemleri kullanacak sınıflara açmalıyız.

Uygulama
  • Yöntemler için ortak bir arayüz tanımlanır. Bu arayüz saf sanal bir sınıf olur. Bu sınıfın dışarıya açılan bir template metodu olur. Dışarıdan bu yöntemleri kullanacak sınıflar template metot üzerinden erişim sağlarlar. Template metot ile uygun yöntem çalıştırılır.
  • Her yöntem için bir sınıf tanımlanır. Bu sınıf ortak arayüz olan saf sanal sınıftan türetme yapılarak oluşturulur. Override edilen metoda uygun yöntem tanımlanır.





public abstract class DokumanYoneticisi {

     public void goruntule() {
         
         //ortak işlemler
         render();
     }
     
     public abstract void render();
}

public class PDF extends DokumanYoneticisi {
    
    public void render() {
        System.out.println("PDF görüntülendi");
    }
}

public class Word extends DokumanYoneticisi {
    
    public void render() {
        System.out.println("Word görüntülendi");
    }
}

public class TEST {
    
    public static void main() {
        DokumanYoneticisi dokumanYoneticisi = new PDF();
        dokumanYoneticisi.goruntule();
    }
}

Eğer sistemimizde içinde bulunduğu duruma göre farklı davranış sergilemesi gereken bir sınıf var ise State Pattern'ını kullanmalıyız. Bu patern bulunduğu duruma göre farklı hareket eden nesneler için bir kalıp sunar. Örneğin bir kaset çalar için çalma durumunda göstereceği davranış ile durma durumunda göstereceği davranış farklıdır. Bu kaset çalar çalma durumundayken çal tuşuna basıldığında bir davranış sergilemezken durma durumunda olduğunda çal tuşuna basıldığında bir davranış sergiler. Bu gibi nesneleri modellerken bu State Pattern'ı kullanılabilir.

Uygulama
  • Nesnenin her bir durumunda farklı davranış sergileyeceği sınıflar oluşturulur.
  • Bu sınıflar ortak bir arayüzü implement ederler.
  • İstemci sadece Context sınıfını bilerek yapmak istediği işlemi uygular. Context sınıfı State arayüzünü kullanarak işlemleri uygular.





public interface IKasetCalarState {
    
    public void cal();
    public void durdur();
}

public class KasetCalarContext {
    IKasetCalarState state;
    
    public KasetCalarContext() {
        IKasetCalarState state = new DuruyorState();
    }
    
    public void cal() {
        state.cal();
    }
    
    public void durdur() {
        state.durdur();
    }
}

public class CaliyorState implements IKasetCalarState {
    
    public void cal() {
        System.out.println("Kaset zaten çalınıyor");
    }
    
    public void durdur() {
        System.out.println("Kaset durduruldu");
    }
}

public class DuruyorState implements IKasetCalarState {
    
    public void cal() {
        System.out.println("Kaset çalınmaya başlandı");
    }
    
    public void durdur() {
        System.out.println("Kaset zaten çalınmıyor");
    }
}

public class Test {
    
    public static void main() {
        KasetCalarContext context = new KasetCalarContext();
        context.cal();
        context.durdur();
    }
}

Bildiğiniz gibi nesneye yönelik programlamanın amacı gerçek dünya nesnelerini yazılıma taşımaktır. Gerçek dünyada bir işlemi yapmak için genelde birden fazla yol vardır. Gerçek dünyada bazen sürekli aynı yolu kullanırken bazen duruma göre farklı yollar kullanabiliriz. Örneğin sabah işe giderken maliyeti düşünerek otobüs tercih edebiliriz ama eğer çok yağmur yağıyorsa o zaman taksiyle gitmek daha mantıklı olabilir. Yazılımda da kalıcı olarak değil duruma göre farklı yollarla bir işi yapmak için geliştirilmiş yöntem Strategy Pattern'ıdır. Bu patern ile bir işin farklı şekilde yapılabilmesi için uygun algoritmalar tasarlanır ve bu işi yapacak nesneler kendileri için uygun yöntemi seçerek işlemlerini gerçekleştirirler. Aynı zamanda bu patern ile sisteme yeni bir yöntem eklenmesi veya var olan bir yöntemin değiştirilmesinden bu yöntemleri kullanan nesneler etkilenmemektedirler. Çünkü yöntemleri kullanan nesneler işin nasıl olacağını bilmezler sadece kendilerine sunulan arayüz üzerinden işlemlerini gerçekleştirirler.

Uygulama
  • Yöntemlerimiz için genel bir arayüz sunacak Strategy soyut sınıfı veya arayüzü tanımlanır.
  • İşi yaptırmak isteyen nesne sadece bu arayüzü bilir ve kullanır.
  • Her bir yöntem için ayrı bir sınıf tanımlanır ve ortak arayüzü implement eder.








public interface Ulasim {
    
    public void git();
}

public class Otobus implements Ulasim {
    
    public void git() {
        System.out.println("Otobüs ile gidildi.");
    }
}

public class Taksi implements Ulasim {
    
    public void git() {
        System.out.println("Taksi ile gidildi.");
    }
}

public class IseGidis {
    private Ulasim ulasim;
    
    public void iseGit() {
        ulasim.git();
    }
}

public class Test {
    
    public static void main() {
        Ulasim otobus = new Otobus();
        Ulasim taksi = new Taksi();
        
        IseGidis iseGidis = new IseGidis();
        iseGidis.iseGit(otobus);
    }
}

Nesne yönelimli programlamanın temellerinden olan türetme her durumda doğru bir şekilde çalışmayabilir. Örneğin bir bankada müşteri tiplerini tanımladığımızı düşünürsek ortak özellikleri müşteri sınıfına koyarak müşteri türüne özel sınıfları bu müşteri sınıfından türetiriz. Bu durumda örneğin türeyen 10 sınıftan 9'unda olacak ama sadece 1'inde olmayacak özelliği üst sınıfa mı koymalıyız yoksa her bir taban sınıfa mı tanımlamalıyız problemi ortaya çıkacaktır. Bu iki seçenekte daha sonradan problem yaratacaktır. Üst sınıfa koymak bazı alt sınıflarda kullanılmayacak özelliğin o sınıflarda var olmasına neden olacaktır. Alt sınıfa koymak ise daha sonra değişiklik yapmayı zorlaştıracaktır. Bu probleme çözüm getiren kalıp Visitor Patern'ıdır. Bu patern ile bazı sınıflarda olacak özellikler o sınıflara dinamik olarak eklenir.

Uygulama
  • Client nesnesini müşteri nesnelerini kullanacak bir sınıf olarak düşünebiliriz.
  • Element arayüzü ise müşteri sınıfı olarak düşünülebilir. Bu arayüzü implement eden nesneler müşteri türlerini oluşturan nesnelerdir.
  • Visitor arayüzü ile client nesne kullanmak istediği ek özelliği dinamik olarak element sınıflarına ekleyebilir.



public abstract class Musteri {
    private int id;
    private String ad;
    //...
    public void accept(IVisitor visitor);
}

public class BireyselMusteri extends Musteri {
    public void accept(IVisitor visitor) {
        visitor.visit(this);
    }
}

public class TicariMusteri extends Musteri {
    public void accept(IVisitor visitor) {
        visitor.visit(this);
    }
}

public interface IVisitor {
    public void visit(Musteri musteri);
}

public class KonutKredisi implements IVisitor {
    public void visit (Musteri musteri) {
        if(musteri instanceof BireyselMusteri) {
            //kredi kullandırım işlemleri
        } else if (musteri instanceof TicariMusteri) {
            //kredi kullanılamaz hatası
        }
    }
}

public class Test {
    public static void main() {
        Musteri musteri1 = new BireyselMusteri();
        Musteri musteri2 = new TicariMusteri();
        KonutKredisi konutKredisi = new KonutKredisi();
        musteri1.visit(konutKredisi);
        musteri2.visit(konutKredisi);
    }
}