DESIGN PATTERNS (1/3)

Zeynep Türker
HardwareAndro
Published in
6 min readMay 19, 2023

--

Herkese Merhaba, bu yazımda design pattern nedir, design pattern çeşitleri ve en çok kullanılan design patternlardan bahsedeceğim.

DESIGN PATTERN NEDİR?

Yazılımcıların, yazılım geliştirme yaparken karşılaştıkları problemleri çözmek için ortak buldukları çözümlere design pattern diyoruz.

Design pattern, algoritma veya kod değildir. Çözüme giderken başvurulan bir stratejidir diyebiliriz. Tek bir programlama diline özgü de değildir. Java C#, C++ gibi bir çok programlama dillerinde kullanılırlar. Design patternlar genelde yazılımcılar için ortak dil olan Uml diagramları üzerinden gösterilirler.

DESIGN PATTERN ÇEŞİTLERİ

  • Creational Design Pattern : Nesnelerin nasıl oluşturulacağı ile ilgilenen bir tasarım desenidir. Bu design pattern ile nesnelerimizi daha doğru bir şekilde oluşturabiliriz.
  • Structural Design Pattern : Nesnelerin bir araya gelmesiyle oluşacak büyük yapıların nasıl oluşacağı ve kullanılacağını ele alan tasarım desenidir.
  • Behavioral Design Pattern : Nesnelerin birbiriyle olan davranışlarıyla ve nasıl haberleştikleriyle ilgilen tasarım desenidir.

Design Pattern’ları doğru bir şekilde kullanırsak kodumuz daha esnek ve yeniden kullanılabilir hale gelir.

CREATIONAL DESIGN PATTERNS

Creational Design pattern türünde en çok kullanılan pattern’lara bakalım.

Singleton Design Pattern

Bir sınıftan sadece tek bir nesnenin oluşturulmasını sağlar.

Database gibi paylaşılan bir kaynağın aynı anda tek bir objesinin olabilmesi sağlanabilir.

Singleton tasarım deseni, global bir variable gibi, programın herhangi bir yerinden bir objeye erişmemizi sağlar.

Şimdi bir örnek üzerinden devam edelim.

public class Singleton {
private static Singleton instance;
public String name = "SINGLETON";

private Singleton() {}

public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

Singleton sınıfı içerisinde nesnemiz private olarak tutuluyor. getInstance methodu ile de bu nesne Client’a veriliyor.

Singleton sınıfımızdaki constructor private olduğu için bu sınıfın nesnesi new ile oluşturulamaz sadece getInstance methodu ile oluşturulabilir.

GetInstance methodunda eğer nesne hiç oluşturulmadıysa oluşturulur, zaten oluşmuş ise nesne return edilir.

Projede birden fazla tread singleton nesnesine ulaşmak isterse bu durumlarda thread safe bir yapı oluşturmalıyız.

public class Demo {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println(singleton.name);
}
}

Client tarafında singleton nesnemizi oluşturmak istediğimizde direk nesne üzerinden getInstance methodunu çağırdık ve nesnemizi aldık.

Factory Design Pattern

Bir sınıfın nesnesinin oluşturulması için gerekli olan işlemler, bu işlemleri gerçekleştirecek bir Factory sınıfına taşıyarak gerçekleştirilir.

Bu tasarım deseni ile nesne oluşumunu Client tarafında değil Factory sınıfının içersinde yaparız. Bu sayede Client’ın, nesnelerimizi nasıl oluşturulacağını bilmesi gerekmez.

Ortak bir parent’a sahip olan Database sınıflarını oluştururken bu design pattern’i kullanabiliriz.

Şimdi bir örnekle devam edelim.

Projemizde MySQL, Oracle ve PostgreSQL database’lerini kullandığımızı varsayalım. Bu durumda bu database’ler ortak bir interface sahip olur.

Database’leri oluştururken direk new Oracle() diye oluşturmak yerine DatabaseFactory sınıfındaki getDatabase methodunu kullanırız.

public enum DatabaseType {
MYSQL, ORACLE, POSTGRESQL
}
public interface Database {
void connect();
}
public class MySQL implements Database{
@Override
public void connect() {
System.out.println("Connected to MySQL database.");
}
}
public class Oracle implements Database{
@Override
public void connect() {
System.out.println("Connected to Oracle database.");
}
}
public class PostgreSQL implements Database{
@Override
public void connect() {
System.out.println("Connected to PostgreSQL database.");
}
}
public class DatabaseFactory {
public Database getDatabase(DatabaseType type) {
if (type == null) return null;

if (type.equals(DatabaseType.MYSQL)) return new MySQL();
else if (type.equals(DatabaseType.ORACLE)) return new Oracle();
else if (type.equals(DatabaseType.POSTGRESQL)) return new PostgreSQL();
return null;
}
}

DatabaseFactory sınıfında ise gelen database tipine göre database’lerin return edildiği bir if else yapımız var.

public class Demo {
public static void main(String[] args) {
DatabaseFactory databaseFactory = new DatabaseFactory();

Database mysql = databaseFactory.getDatabase(DatabaseType.MYSQL);
mysql.connect();

Database oracle = databaseFactory.getDatabase(DatabaseType.ORACLE);
oracle.connect();

Database postgre = databaseFactory.getDatabase(DatabaseType.POSTGRESQL);
postgre.connect();
}
}

Client tarafında database’lerimizi oluşturmak istediğimizde direk Factory sınıfını oluşturmamız ve getDatabase methodunu çağırmamız yeterli oluyor. Client’ın database alt sınıflarının oluşumuyla ilgilenmesine gerek kalmıyor.

Abstract Factory Design Pattern

İlişkisel olan birden fazla nesnenin üretimini tek bir arayüz tarafından değil her ürün ailesi için farklı bir arayüz tanımlayarak sağlamaktadır. Yani factory design patternler’ı fabrika olarak düşünürsek, Factory tasarım deseni tek bir ürünün üretildiği fabrika, Abstract Factory tasarım deseni ise farklı ürünlerin üretildiği fabrika olarak düşünülebilir.

Şimdi bir örnek üzerinden daha iyi anlamaya çalışalım.

Projemizde sandalye ve masa şeklinde iki farklı ürünümüz olsun. Her ürünün de kendi içinde Modern ve Victorian şeklinde iki modeli olsun.

Şimdi ürün interface’lerimizi ve onların alt sınıflarına da modellerimizi ekleyelim.

public interface Chair {}
public interface Table {}
public class VictorianChair implements Chair {}
public class VictorianTable implements Table {}
public class ModernChair implements Chair {}
public class ModernTable implements Table {}

Peki şimdi bu ürün modellerine nasıl ulaşacağız? Client tarafında new VictorianTable() şeklinde nesneler mi oluşturacağız? Hayır.

Factory sınıfları kullanarak ürün modelleri oluşturacağız. Bu yüzden de Client, ürün modellerini bilmeyecek. Fabrika üzerinden ürünleri alacak sadece.

Şimdi her ürün modeli fabrikası için bir interface yazalım.

public interface FurnitureFactory {
Chair createChair();
Table createTable();
}

Eklenecek her model için fabrika oluşturacağız ve FurnitureFactory’deki methodlarda ürün modellerimizi oluşturmuş olacağız.

public class VictorianFurnitureFactory implements FurnitureFactory {
@Override
public Chair createChair() {
return new VictorianChair();
}

@Override
public Table createTable() {
return new VictorianTable();
}
}
public class ModernFurnitureFactory implements FurnitureFactory {
@Override
public Chair createChair() {
return new ModernChair();
}

@Override
public Table createTable() {
return new ModernTable();
}
}

Gördüğümüz üzere her model fabrika sınıfı kendi içinde sandalye ve masayı oluşturmuş oldu.

Peki ya Client tarafında bu ürün modellerini nasıl oluşturacağız?

İşte bu şekilde..

public class Demo {
public static void main(String[] args) {
FurnitureFactory victorianFurnitureFactory = new VictorianFurnitureFactory();
Table victorianTable = victorianFurnitureFactory.createTable();
Chair victorianChair = victorianFurnitureFactory.createChair();

FurnitureFactory modernFurnitureFactory = new ModernFurnitureFactory();
Table modernTable = modernFurnitureFactory.createTable();
Chair modernChair = modernFurnitureFactory.createChair();
}
}

Client’da direk polimorfizm ile FurtinuteFactory objesi üzerinden model factorylerimizi oluşturuyoruz. Kullanıcının istediği ürün modeline göre methodları çağırıyoruz. Bu sayede factory design pattern’daki if else bloklarından kurtulmuş olduk.

Builder Design Pattern

Builder pattern, karmaşık nesneleri adım adım oluşturmamıza olanak tanıyan creational bir tasarım desenidir.

Karmaşık sınıflarda çok fazla field olabiliyor. Bu yüzden de bu sınıfların birden fazla constructor’ı ve constructor’larındaki parametre sayısı da çok olabiliyor. Bu durumlarda yazılımcının kafası karışabiliyor. Böyle zamanlarda Builder Design Pattern’ı kullanabiliriz. Zorunlu olan field’ları Builder ile direk alırız. Geri kalan zorunlu olmayan field’ları ise isteğe göre Client’tan alırız.

Bir örnek üzerinden devam edelim.

User isimli bir sınıfımız olsun. User sınıfında isim, soyisim, yaş, adres ve telefon numaraları gibi bilgiler var. İsim ve soyisim dışındaki veriler de zorunlu olmasın. User, constructor’ında UserBuilder’ı alarak içerisindeki bilgileri alır.

User Builder inner ve static classtır yani UserBuilder sınıfını oluştururken direk User sınıfı üzerinden UserBuilder’ı oluşturabiliriz. UserBuilder sınıfı User sınıfındaki fieldlara sahip olmalı.

UserBuilder constructor’ında User sınıfında zorunlu olan verileri alır. Yani isim ve soyisim. Diğer zorunlu olmayan fieldlar ise herbiri için method yazarak bu methodlar yardımıyla gerekli atamaları yaparız.

Build methodu ile de User nesnemiz return edilir.

public class User {
private final String name; // required
private final String surname; // required
private final int age; // optional
private final String phone; // optional
private final String address; // optional

private User(UserBuilder builder) {
this.name = builder.name;
this.surname = builder.surname;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}

public String getName() {
return name;
}

public String getSurname() {
return surname;
}

public int getAge() {
return age;
}

public String getPhone() {
return phone;
}

public String getAddress() {
return address;
}

@Override
public String toString() {
return "User: " + this.getName() + ", " + this.getSurname() + ", " + this.getAge() + ", " + this.getPhone() + ", " + this.getAddress();
}

public static class UserBuilder {
private final String name;
private final String surname;
private int age;
private String phone;
private String address;

public UserBuilder(String name, String surname) {
this.name = name;
this.surname = surname;
}

public UserBuilder age(int age) {
this.age = age;
return this;
}

public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}

public UserBuilder address(String address) {
this.address = address;
return this;
}

public User build() {
return new User(this);
}
}
}

public class Demo {
public static void main(String[] args)
{
User user1 = new User.UserBuilder("Zeynep", "Türker")
.age(25)
.phone("05075869595")
.address("Harmandere Mah. Site Sok. No: 6/2 Birkent Sitesi A Blok Daire 10")
.build();

System.out.println(user1);

User user2 = new User.UserBuilder("Çağla", "Şen")
.age(24)
.phone("05075965656")
.build();

System.out.println(user2);

User user3 = new User.UserBuilder("Nazire", "Türker")
.build();

System.out.println(user3);
}
}

Yukarıda da görüldüğü üzere User objelerini oluştururken sadece bize gerekli olan bilgileri girdik.

Builder design pattern sayesinde nesnemizi daha kolay bir şekilde oluşturabildik.

Umarım faydalı olmuştur.

Bu yazının devamında en çok kullanılan structural design patternlardan bahsettim. Bu yazıya da bakabilirsiniz.

--

--