Kod Esnekliğini Arttırın: Strategy Design Pattern

Duygu Orhan
folksdev
Published in
5 min readApr 24, 2024

Design Patterns yani tasarım kalıpları zaman içerisinde yazılım geliştiricilerin yazılım geliştirirken karşılaştıkları sorunlara buldukları çözümlerdir. Bu sorunlar ve çözümler önceden belirlendiği için geliştirme sürecini bir yazılımcı için hızlandırır. Design patterns 3 ana başlık altında incelenir. Creational, Structural, Behavioral.

Bu yazıda bizim sorunumuzu çözecek olan Behavioral Patterns (Davranışsal Kalıplar) türünden Strategy Design Pattern modeli. Behavioral patterns nesnelerin çalışma sırasında davranış şekillerinin değişebilmesini sağlayan çözümler sunan tasarım kalıplarını içermektedir.

Strategy Design Pattern modelinde, bir nesnenin davranışı veya algoritması çalışma zamanında değiştirilebilir. Burada oluşturulan nesne dinamiktir, birden fazla davranış veya algoritma olduğundan çalışma zamanında seçim yapılmasına sağlanır. Burada birden fazla davranış olduğu için birden fazla da metot kullanılacaktır amaç ortak metodu bulmak ve bunu bir interface de tanımlamak. O davranışı sergilemesi gereken sınıflarda implementasyon yaparak içlerini o sınıfa uygun olacak şekilde doldurmak. Sonrasında ise bu oluşturulan sınıfları kullanabilmek için ayrı bir sınıf oluşturarak interface sınıfını dahil etmek ve gelen davranış şekillerine göre ilgili sınıflara yönlendirmek. Buraya gelen istekler, istenilen davranışın sınıfına giderek istenilen metodu çalıştıracaktır.

Aşağıdaki örnekte bir problem verildi ve bu problem herhangi bir design pattern kullanılmadan nasıl yapıldığı ve startegy design pattern kullanarak nasıl yapıldığı çözümlenerek gösterildi.

Problem: Bir X bankası gelen müşterilerini hesap türlerine göre ilgili işlem sayfalarına yönlendirmek istiyor. Bu müşteri bireysel, kurumsal ya da henüz hesabı olmayan bir müşteri olabilir. Her bir işlem içerik olarak farklı olsa da yapacağımız iş sabit, müşterileri ilgili sayfalara yönlendirmek.

Çözüm 1: If-else yapısını kullanmak. Programa gelen o anki istekler doğrultusunda seçilen hesap türüne göre işlemler bir if else yapısından geçerek gerçekleşecektir. Ancak burada başka bir hesap eklemek istediğimizde ileriki süreçlerde kod gittikçe karmaşıklaşacaktır. Her bir hesap türü için if döngüsü eklenecektir. Bu kodu kullanmamız programın büyüdükçe kod kalitesini ve okunurluğunu düşürecektir.

Çözüm 2: Strategy Design Pattern kullanmak. Programa gelen o anki istekler doğrultusunda direkt olarak seçilen hesap türüne göre işlemlere yönlendirilerek gerçekleşecektir. Ve burada ileriki süreçlerde farklı bir hesap türü eklenmek istendiğinde sadece ilgili sınıfın eklenerek implementasyonların yapılması ve içlerinin istenilen davranışa uygun şekilde doldurulması yeterli olacaktır.

Öncelikle spring initializer sayfasına giderek bir SpringBoot projesi oluşturuyoruz. (Dependencys: Lombok, Spring Web, SpringBoot DevTools)

Proje yapısı

Bu proje yapısında manuel olarak açılan paket çözüm 1'in kodlarını, strategy olarak açılan paket ise çözüm 2'nin kodlarını içermektedir.

Çözüm 1 :

Öncelikle burada bir servis sınıfı oluşturuyoruz ve kullanıcı hesabı türlerinin metotlarını ve içlerine de yapması gereken işlemleri buraya yazıyoruz. Nasıl çalıştığını görmek açısından metotlar String tipinde tanımlandı ve değerleri döndü.

@Service
public class AccountManuelService {

public String individualAccountForward(){
return "You are directed to the individual account.. ";
}

public String corporateAccountForward(){
return "You are directed to the corporate account.. ";
}

public String notRegisteredAccount(){
return "You are directed to the register page.. ";
}

}

Oluşturduğumuz AccountManuelService’i aşağıda oluşturduğumuz CustomerForwardManuelService sınıfına inject ederek oluşturduğumuz customerForward metodunda kullanıcının gönderdiği tipe göre hangi işlemi seçmesi gerektiğini if else bloklarıyla tanımlıyoruz. Burada şu anlık 3 tanımlı davranış şeklimiz olduğu için 3 if yapısı bulunmakta ama daha sonrasında programa yeni bir hesap eklenmek istenildiğinde;

  • AccountManuelService sınıfına giderek istenilen metodu oluşturacağız,
  • CustomerForwardManuelService sınıfına gelerek bir else if daha ekleyerek statik olarak bu işlemi gerçekleştireceğiz.
@Component
public class CustomerForwardManuelService {
private final AccountManuelService accountManuelService;

public CustomerForwardManuelService(AccountManuelService accountManuelService) {
this.accountManuelService = accountManuelService;
}

public String customerForward(String type){
if(type.equals("individual")) return accountManuelService.individualAccountForward();
else if(type.equals("corporate")) return accountManuelService.corporateAccountForward();
else if (type.equals("notRegistered")) return accountManuelService.notRegisteredAccount();
else return "An Invalid Account: " + type;

}
}

Kullanıcıdan gelen istekleri yönlendirildiği sınıf olan AccountManuelController sınıfında, kullanıcıdan gelen hesap tipini alıp customerForwardService sınıfında oluşturduğumuz metodu çağırarak içine hesap tipini yazıyoruz. Burada uygun cevap döndürülecektir.

@RestController
@RequestMapping("/api/v1/account")
@RequiredArgsConstructor
public class AccountManuelController {
private final CustomerForwardManuelService customerForwardService;

@GetMapping("/{type}")
public String getAccountType(@PathVariable String type) {
return customerForwardService.customerForward(type);
}
}

Ve Postman’de atılan isteğin sonucu:

Kurumsal hesap çağırma sonucu

Çözüm 2: Yeniden düzenlemiş olduğumuz, düzenlerken de strategy design pattern’ı kullandığımız Çözüm 2'yi inceleyelim.

Strategy desin pattern’nın ana mantığı aşağıdaki sınıf diyagramında gösterildi. AccountService interface’i ve onu kullanarak oluşan sınıflar (IndividualAccountService, CorporateAccountService, NotRegisteredAccountService) ve bu sınıfları kullanıp yönlendirmesi için tanımlanan CustomerForwardStrategyService sınıfı. İstenilen işlemin gerçekleşebilmesi için kullanılacak olan tek sınıf CustomerForwardStrategyService’dir.

Strategy Design Pattern sınıf diyagramı

Burada öncelikle AccountType adına bir Enum sınıfı oluşturuyoruz. Bu sınıfta bizim hangi hesap türlerinin mevcut olduğunu tutulacak.

public enum AccountType {

INDIVIDUAL,
CORPORATE,
NOT_REGISTERED
}

AccountService adında bir interface oluşturarak accountForward ve isApplicable metotlarını tanımladık. Bunlar her hesap türünde var olan ancak içerikleri farklı olan metotlardır.

public interface AccountService {

String accountForward();

boolean isApplicable(String type);

}

Oluşturduğumuz interface’i hesap türlerinin işlemlerini gerçekleştirdiğimiz sınıflara uygulayabiliriz. Aşağıda kurumsal hesap türü için CorporateAccountService sınıfını tanımladık. Bu sınıf için uyguladığımız AccountService’in metotları gelmiş ve corporate hesap türünde gerçekleşecek işlemler koda aktarılmıştır.

@Service
public class CorporateAccountService implements AccountService {
@Override
public String accountForward() {
return "You are directed to the corporate account.. ";
}
@Override
public boolean isApplicable(String type) {
return AccountType.CORPORATE.toString().equals(type);
}
}

Bireysel hesap türü için:

@Service
public class IndividualAccountService implements AccountService {
@Override
public String accountForward() {
return "You are directed to the individual account..";
}

@Override
public boolean isApplicable(String type) {
return AccountType.INDIVIDUAL.toString().equals(type);
}
}

Sonrasında bir servis sınıfı daha oluşturduk. AccountService ile oluşturduğumuz tüm hesapları almak için burada işlemler gerçekleştiriyoruz. Eğer tüm hesaplar AccountServis ile tanımlanırsa o hesaplara bir liste şeklinde ulaşabiliriz. Bu sınıfta gelen hesap türüne göre uygun olan ve tanımlı olan hesap türünü bulur sonrasında ilgili servis sınıfına giderek programı çalıştırır.

@Component
public class CustomerForwardStrategyService {


private final List<AccountService> accountServiceList;

public CustomerForwardStrategyService(List<AccountService> accountServiceList) {
this.accountServiceList = accountServiceList;
}
public String customerForward(String type){

if (accountServiceList.isEmpty()) {
throw new BusinessException("Don't Found Account!");
}

Optional<String> result= accountServiceList.stream()
.filter(accountService -> accountService.isApplicable(type))
.findFirst()
.map(accountService -> accountService.accountForward());

return result.orElseThrow(()-> new BusinessException("An Invalid Account: " + type));

}

}

Kullanıcıdan gelen istekleri yönlendirildiği sınıf olan AccountController sınıfında, kullanıcıdan gelen hesap tipini alıp customerForwardStrategyService sınıfında oluşturduğumuz metodu çağırarak içine hesap tipini yazıyoruz. Burada uygun cevap döndürülecektir.

@RestController
@RequestMapping("/api/v2/account")
@RequiredArgsConstructor
public class AccountController {

private final CustomerForwardStrategyService customerForwardStrategyService;


@GetMapping("/{type}")
public String getAccountType(@PathVariable String type) {
return customerForwardStrategyService.customerForward(type);
}
}

Ve Postman’de atılan isteğin sonucu:

Kurumsal hesap çağırma sonucu
Böyle bir hesap türü tanımlanmadı, hata mesajı gönderdi

Eğer yeni bir hesap türü eklemek isteseydik yapmamız gereken tek şey ilgili hesap türü ile bir servis sınıfı açmak ve sınıfta AccountServis’i uygulamak. Başka hiç bir sınıfa dokunmamıza gerek kalmayacak.

Bu kodlara ulaşmak isterseniz Github Repository buradadır.

Okuduğunuz için teşekkür ederim. Umarım sizler için faydalı bir yazı olmuştur 🫶🏻

Eğer yazılarım ilginizi çekiyorsa beni takip etmeyi unutmayın ✨💐
X | LinkedIn | GitHub

Bir sonraki yazıda görüşmek üzere, sağlıcakla kalın🙌🏻

--

--