Chain of Responsibility pattern, adı üzerinde birden çok responsibility'nin (sorumluluk) yani işleyişin bir zincir gibi birbirlerine bağlı ve belirlenen bir sıra ile yürütülüp işlenmesini sağlar.
Aşağıdaki şemada görüldüğü üzere bir authentication işleminin alt responsibility'ler ile bölünmesi ve bu responsibility'lerin sırası ile çalıştırılarak authentication işleminin yapılması örneklenmiştir.
Bu işlem, işlevlerin bir LinkedList veri tipi gibi birbirlerinin sıradaki handler'larını göstererek sıralı ve bağlı bir şekilde işlemlerin gerçekleştirilmesini sağlamaktadır.
Yukarıdaki örneğin implementasyonu yapılacak olursa öncelikle authentication işleminin gerçekleştirileceği User class'ının oluşturulur.
public class User
{
public string Email { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}Daha sonrasında bu küçük responsibility'lerin implemente edileceği class'ları imzalayacak olan IHandler interface'i tanımlanır.
public interface IHandler<T>
{
void SetNext(IHandler<T> handler);
void HandleRequest(T request);
}Bu interface'in içerisindeki fonksiyon imzalarına bakılacak olursa SetNext(IHandler<T> handler) fonksiyonu bir sonraki çalışacak handler'ın seçilmesini sağlayacak olan implementasyonu barındırmaktadır. HandleRequest(T request) methodu ise handle edilecek responsibility'nin implementasyonunu barındıran bir fonksiyondur.
Bununla birlikte AuthenticationHandler class'ının tanımlanması ve responsibilitylerin bu handler class'dan inherit alınarak implemente edilmesi gerekmektedir.
public abstract class UserAuthenticationHandler : IHandler<User>
{
private IHandler<User> _nextHandler;
public void SetNext(IHandler<User> handler)
{
_nextHandler = handler;
}
public virtual void HandleRequest(User request)
{
if (_nextHandler != null)
{
_nextHandler.HandleRequest(request);
}
}
}Yukarıdaki UserAuthenticationHandler abstract class'ında yapılan implementasyonlara bakılacak olursa SetNextfonksiyonu bir sonraki handler'ın ne olacağını belirliyor ve kendi içinde saklıyor. HandleRequest virtual methodu ise bu base handler class'ını inherit alacak olan classların override etmesi ve en sonda base fonksiyonu çağırarak sıradaki responsibility'nin handle edilmesini sağlayacak şekilde oluşturulmuştur.
public class ValidateEmail : UserAuthenticationHandler
{
public override void HandleRequest(User request)
{
if (!string.IsNullOrEmpty(request.Email) && request.Email.Contains("@"))
{
Console.WriteLine("Email is valid.");
base.HandleRequest(request);
}
else
{
Console.WriteLine("Invalid email address.");
}
}
}
public class UserExists : UserAuthenticationHandler
{
public override void HandleRequest(User request)
{
if (UserDatabase.Users.ContainsKey(request.Username))
{
Console.WriteLine("User exists in the database.");
base.HandleRequest(request);
}
else
{
Console.WriteLine("User does not exist in the database.");
}
}
}
public class AuthenticateUser : UserAuthenticationHandler
{
public override void HandleRequest(User request)
{
// Authentication operations...
if (!string.IsNullOrEmpty(request.Username) && !string.IsNullOrEmpty(request.Password))
{
Console.WriteLine("User authenticated successfully.");
base.HandleRequest(request);
}
else
{
Console.WriteLine("Authentication failed.");
}
}
}Yukarıda görüldüğü üzere en baştaki şemada belirtilen responsibility'lerin implementasyonları gerçekleştirilmiştir. ValidateEmail class'ı email'in düzgün olup olmadığını, UserExists class'ı aşağıdaki bu örnek özelinde inMemory çalışacak Database'i simüle eden class ile database'de ilgili user olup olmadığını kontrol edecek responsibility'yi implemente ediyor ve AuthenticateUser class'ı da chain'in sonunda authenticate etme implementasyonlarını barındırıyor.
public static class UserDatabase
{
public static Dictionary<string, User> Users => new Dictionary<string, User>()
{
{ "Emopusta", new User { Email = "emopusta@is.pro", Username = "Emopusta", Password = "ImPro" } }
};
}Sonrasında Authenticate işlemlerini tek bir yerde toplayıp daha temiz bir gösterim yapılabilmesi için aşağıdaki gibi AuthService adında bir sınıfın içerisinde Authenticate metodunda responsibility'lerin chain operasyonu yapılmıştır.
public class AuthService
{
public void Authenticate(User user)
{
UserAuthenticationHandler validateEmail = new ValidateEmail();
UserAuthenticationHandler userExists = new UserExists();
UserAuthenticationHandler authenticateUser = new AuthenticateUser();
validateEmail.SetNext(userExists);
userExists.SetNext(authenticateUser);
validateEmail.HandleRequest(user);
}
}Son olarak aşağıdaki implementasyonda nasıl kullanılabileceği ve örnekleri mevcuttur.
internal class Program
{
private static void Main(string[] args)
{
var authService = new AuthService();
// Create a valid user
User user = new User
{
Email = "emopusta@is.pro",
Password = "ImPro",
Username = "Emopusta"
};
authService.Authenticate(user);
Console.WriteLine("*********");
// Test with an non existent user
User invalidUser = new User
{
Email = "e@e",
Password = "",
Username = "NonExistentUser"
};
authService.Authenticate(invalidUser);
Console.WriteLine("*********");
// Test with a user that exists but has an invalid email
User existingUserWithInvalidEmail = new User
{
Email = "invalidEmail",
Password = "ImPro",
Username = "Emopusta"
};
authService.Authenticate(existingUserWithInvalidEmail);
Console.WriteLine("*********");
}
}Yukarıda görüldüğü gibi farklı senaryolarda kullanıcılar authenticate edilmeye çalışılmış olup responsibility chain üzerinden işlemler gerçekleştirilmiştir.
