Welcome to My Blog 👋

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

Bildiğiniz gibi kullanıcılar sadece uygulamamızın arayüzü ile etkileşim içinde olduğundan dolayı kullanıcılar için genellikle iyi bir uygulama, tasarımı güzel uygulama oluyor. Ben de kod yazma konusunda kendini geliştirmiş ama tasarım konusunda eksik bir insan olarak kullanıcıların gözüne hoş gelebilecek tasarımlar yapma konusunda zorlanıyordum. Ayrıca mobil cihazların ekran boyutlarındaki farklılıktan dolayı yaptığımız tasarımların her cihazda farklı görünebilmeside başka bir problem olarak beni zorluyordu. Bir süredir bu konuda araştırma yapıyordum ve bu problemleri çözme konusunda yardımcı olabilecek bir çok araç keşfettim. Ayrıca fark ettiğim bir diğer konu bu araçlar konusunda türkçe kaynağın çok az olmasıydı. Bu yüzden bu araçların ne olduğu hakkında ve bu araçları kullanma konusunda kendimi geliştirdikçe nasıl kullanıldıklarına dair yazılar yazmaya karar verdim. Bu araçlardan ilk anlatıcak olduğum Figma isimli mobil uygulama tasarlama aracıdır.

Mobil Uygulama Tasarım Aracı Nedir?

Aklınıza bir mobil uygulama tasarımı geldiği zaman veya bir mobil uygulama yapacağınız zaman bu tasarımı kağıt üzerine çizebilir daha sonra kodlama ile bu tasarımı gerçekleştirebilirsiniz. Ancak bu noktada ciddi bir problem ortaya çıkıyor. Kağıt üzerine çizdiğiniz tasarım sadece elemanların nasıl yerleşeceği, nerede ne olacağı ile ilgili oluyor. Oysa tasarımda renklerin uyumu dahi başlı başına üzerine ciddi düşünülmesi gereken bir konuyken bu problemleri kağıt üzerinde çözmek neredeyse imkansızdır. Ancak uygulamanızı yaptığınız zaman gerçekten tasarımı net bir şekilde görebilirsiniz. Bu problemlerin çözümü kağıda çizdiğiniz tasarımı kodlamadan önce gerçek bir cihaza bakıyormuş gibi karşınızda görebilmeniz ile olabilir. Bu noktada karşımıza mobil uygulama tasarım araçları çıkıyor. Aslında bu araçlar basit resim düzenleme araçlarından farksızdır. Bu araçlarda bulunan komponentler ile çok fazla çizim yapmadan sürekle bırak ile kolaylıkla mobil uygulama yapabilirsiniz. Ayrıca genellikle bu araçlar çok karmaşık resim düzenleme uygulamalarından farklı olarak daha basitleştirilmiş ve bu konuda özelleşmiş araçlardır. Bende bu yazımda bu araçlardan biri olan Figma'dan ve alternatifleriyle olan farklılıklarından da bahsediceğim.



Figma Nedir?

Figma farklı ekran görüntülerine göre tasarım yapmanıza olanak sağlayan, birden fazla kişi ile sekronize olarak çalışabildiğiniz, tasarıyıcıda veya tüm bilgisayarlarda çalışabilen, farklı türlerde çıktı alabildiğiniz bir arayüz tasarım aracıdır.

Figma'nın Özellikleri ve Alternatifleri ile Farkları

  • Figma rakibi olan Sketch'e göre tüm platformlarda çalışabilir. Yani isterseniz bir tarayıcıda açabilirsiniz, mac bilgisayarınıza indirip kurabilirsiniz veya windows makinenizde de rahatlıkla kullanabilirsiniz. Sketch ise sadece mac'lerde kullanılabilen bir arayüz tasarım aracıdır.
  • Figma neredeyse tamamen ücretsiz bir araçtır. Eğer profesyonel bir ekip ile sekronize bir şekilde bir proje üzerinde çalışmak istiyorsanız ücret ödemeniz gerekiyor. Bunun dışında tüm özellikleri ücretsizdir. Sketch'i ise yıllık olarak 99 dolar gibi bir ücret ödeyerek kullanabiliyorsunuz.
  • Figma bir proje üzerinde ekibinizle sekronize bir şekilde çalışabilmenize imkan sağlıyorken Sketch ile bunu şimdilik yapamıyorsunuz.
  • Figma ile yaptığınız tasarımları kolaylıkla arkadaşlarınız ile paylaşabiliyorsunuz ve arkadaşlarınız Figma üzerinden bu tasarımı görüntüleyebiliyor.
  • Sketch ile oluşturduğunuz tasarımları Figma destekliyor. Yani daha önceden Sketch ile yaptığınız tasarıma Figma ile devam edebilirsiniz.
  • Figma ile yaptığınız tasarımların farklı ekran boyutlarında nasıl görüneceğini kolaylıkla test edebilirsiniz.
  • Bileşen kütüphanesi oluşturabilirsiniz. Örneğin bir kaç buton tasarımı yaptınız ve projenizde bu butonların kullanılması gerekiyor. Bu tasarımı kütüphane haline getirerek arkadaşlarınız ile paylaşabilir ve onlarında aynı butonları kullanmasını sağlayabilirsiniz.





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);
    }
}

Günlük hayatta kullandığımız bir çok cihazın nasıl çalıştığı konusunda bilgi sahibi olmayız. Bize sunulan arayüzleri yani komutları kullanarak cihazlarda istediğimiz işlemleri yapabiliriz. Cihaz üreticilerinin yaptıkları güncellemelerden etkilenmeden cihazları kullanmaya devam edebiliriz. Yazılım sistemlerinde de bir nesne başka bir nesneye işlem yaptıracağı zaman o nesnenin nasıl çalıştığını bilmemelidir. Çünkü eğer bir nesne diğer nesnenin nasıl çalıştığını bilirse aralarında sıkı bağ olur ve bu durum koddaki bağımlılığı arttırır. Bağımlılık ise kodda güncelleme yapmayı zorlaştırır. Bu duruma çözüm getiren kalıp Command Patern'ıdır. Bu patern ile karmaşık işler yapan nesneler kendilerini kullanacak nesnelere arayüzler sağlar. İş yaptıracak nesneler bu arayüzler ile işin nasıl yapıldığını bilmeden o işi yaptırabilirler.
Örneğin ATM cihazlarının nasıl çalıştığını bilmeden ekran ve klavyeyi kullanarak makineye istediğimiz işlemleri yaptırabiliriz. Burada klavye, ekran ve diğer girişler bize sunulan arayüzlerdir.

Uygulama
  • Client command arayüzlerini kullanarak işlemlerini yaptırır.
  • Receiver nesnesine (ATM) arayüzler tarafından işlemler yaptırılır.
  • Invoker nesnesi (Klavye) command arayüzlerini barındırır. Kullanıcıya makineyi kullanması için arayüzler sağlar.




public class ATM{

     public void giris() {
         //işlemler
     }
     
     public void sil() {
         //işlemler
     }
     
     public void onayla() {
         //işlemler
     }
}

public interface Command {
    public void execute();
}

public class GirisCommand implements Command {
    private ATM atm;
    public void execute () {
        atm.giris();
    }
} 

public class SilCommand implements Command {
    private ATM atm;
    public void execute () {
        atm.sil();
    }
}

public class OnaylaCommand implements Command {
    private ATM atm;
    public void execute () {
        atm.onayla();
    }
}

public class Klayve {
    private Command command;
    
    public void setCommand (Command command) {
        this.command = command;
    }
    
    public void tusaBas () {
        command.execute();
    }
}

public class Test {
    public static void main() {
        Klavye klavye = new Klavye();
        Atm atm = new ATM();
        Command girisCommand = new GirisCommand(atm);
        Command silCommand = new SilCommand(atm);
        klavye.setCommand(girisCommand);
        klavye.tusaBas();
        klavye.setCommand(silCommand);
        klavye.tusaBas();
    }
}

Chain Of Responsibility patern'ı bir dizi işlemi o işlemin karmaşıklığını bilmeden gevşek bağlı bir şekilde yapmamızı sağlar. Bu şekilde istemcide bir değişiklik yapmadan yapılan işlem değiştirilebilir. Yapılacak işlem birden fazla nesnenin kullanılmasını gerektiriyor ve bu nesnelerin yaptıkları işlemlerin belli bir sırası var ise bu pattern kullanılır. Yani istemcinin tek bir işlem için birden fazla nesneyi bilmesi ve onları bu işlem için sırasıyla çalıştırması yerine bu pattern ile işlem tanımlanır ve nesneler kendi aralarında iletişim kurarak bu işlemi gerçekleştirirler.
Örnek verecek olursak masaüstü programlamada bir pencere içerisinde bir başka penceremizin olduğunu ve onun içinde bir resim olduğunu düşünürsek o resme tıklanma event'ini o resim, içinde bulunduğu pencere ve o pencerenin içinde bulunduğu pencere yakalamak isteyebilir. Bunun için bu event içinde bulunduğu tüm view elemanlarına hiyerarşik bir şekilde gönderilir. Bu işlem sırasında bir zincir şeklinde her view içinde bulunduğu view elemanını bilir.

Uygulama
  • İstekleri karşılayacak ve bir zincir şeklinde işleyecek sınıflar için bir arayüz olan Handler arayüzü tanımlanır. Bu arayüzün istekleri karşılayacak handle metodu yazılır.
  • İstekleri yerine getirecek sınıflar Handler arayüzünü implement ederler.
  • Client nesne Handler arayüzünü kullanarak istekleri yerine getirecek sınıfları kullanır.




public interface Handler {
    public void handleRequest();
}

public class HandlerConcrete1 implements Handler {
    Handler handler = new HandlerConcrete2();
    public void handleRequest() {
        //işlemler
        handler.handleRequest();
    }
}

public class HandlerConcrete2 implements Handler {
    public void handleRequest() {
        //işlemler
    }
}

public class Client {
    public static void main () {
        Handler handler = new HandlerConcrete1();
        handler.handleRequest();
    }
}



Nesneye yönelik programlama tekniğinde yazılımımızı nesneler halinde tasarlarız. Bu nesneler aralarında iletişim halindedirler. Bu iletişim ile bu nesneler arasında bir bağımlılık oluşur. Yazılım sistemlerinde bağımlılık istenmeyen bir durumdur. Çünkü bağımlılık demek yazılım sistemindeki bir değişikliğin yapılmasının zorlaşması demektir. Sistem büyüdükçe bağımlılıklar o kadar artarki değişiklik yapılması imkansız bir hale gelebilir. Bu problemi çözmek için mediator pattern'ı kullanılabilir. Bu pattern ile iki nesne birbirine bağımlı olmadan birbirini bilmeden birbiri ile iletişim kurabilir. Bunu sağlayan yapı, iletişim kuracak iki nesne arasına bir nesne daha koyarak iletişimi bu nesne üzerinden yapar.
Örneğin bir havalimanında pisti kullanacak uçaklar birbirleri ile iletişim kurmazlar. Çünkü böyle bir durumda inanılmaz bir karmaşıklık ortaya çıkar. Tüm uçaklar sadece kuleyle iletişim kurarak pistin kullanımı sekronize edilir.

Uygulama
  • Birbirleri ile iletişim kuracak nesneler mediator interface'ini barındırırlar ve kullanırlar.
  • İletişime özel mediator sınıfları tanımlanır ve mediator interface'i implement edilir.
  • Bir nesne diğer nesneler ile iletişim kurmak istediğinde mediator interface'ini kullanarak özel tanımlanan metotlar üzerinden iletişim kurar.




public class IstanbulKule implements Kule{
    public void ucakEkle (Ucak ucak) {
        ucaklar.add(ucak);
    }
    public void ucakCikar (Ucak ucak) {
        ucaklar.remove(ucak);
    }
    public void haberVer (Ucak ucak, String mesaj) {
        ucak.mesajAl(mesaj);
    }
}

public interface Kule {
    ArrayList<Ucak> ucaklar;
    public void ucakEkle(Ucak ucak);
    public void ucakCikar(Ucak ucak);
    public void haberVer(Ucak ucak, String mesaj);
}

public class UcakModel1 {
    public Kule kule = new IstanbulKule();
    public void haberAl (String mesaj) {
        System.out.println("UcakModel1 Haber alındı." + mesaj);
    }
}

public class UcakModel2 {
    public void haberAl (String mesaj) {
        System.out.println("UcakModel2 Haber alındı. " + mesaj);
    }
}

public interface Ucak () {
    public void haberAl(String mesaj);
}

public class Test {
    public static void main () {
        UcakModel1 ucak1 = new UcakModel1();
        UcakModel2 ucak2 = new UcakModel2();
        ucak1.kule.haberVer(ucak2, "Pisti kullanıyorum");
    }
}