Bu makalede SOLID prensiplerinin ikinci maddesinden söz edeceğiz. "Neydi bu prensipler?" diye mi sordunuz, buyurun, özet makaleyi inceleyin.
Bir uygulamanın ömrü boyunca, sürekli olarak sıfırdan yeni özellikler eklemekten ziyade mevcut kod temeline ekleme yapmak için daha çok zaman harcanır. Belki farkında olduğunuz gibi, bu zahmetli ve can sıkıcı bir süreç olabilir. Kodunuzu değiştirdiğiniz her zaman, yeni hatalar ekleme veya eski işlevselliği tamamen bozma riskini taşırsınız. İdeal olanı, mevcut kod temelimizi, yepyeni bir kod yazarken olduğu kadar çabuk ve kolay bir şekilde değiştirebilmemizdir. Eğer uygulamamızı Açık Kapalı ilkesine uyacak şekilde tasarlarsak, bunu yapabiliriz demektir!
SOLID tasarımın Açık Kapalı ilkesi kodun genişletmelere açık ama değişikliklere kapalı olmasını belirtir.
Açık Kapalı ilkesini göstermek için, önceki bölümdeki OrderProcessor ile çalışmaya devam edelim. Bu sınıfın `process` metodundaki aşağıdaki kesimi ele alalım:
$recent = $this->orders->getRecentOrderCount($order->account);
if ($recent > 0)
{
throw new Exception('Muhtemelen aynı sipariş ikilendi.');
}
Bu kod oldukça okunaklıdır ve bağımlılık enjeksiyonunu düzgün bir biçimde kullandığımız için test edilmesi de kolaydır. Ancak, sipariş doğrulamasıyla ilgili iş kurallarımızı değiştirirsek ne olacak? Yeni kurallar alırsak ne olacak? Gerçekte, işimiz büyüdükçe birçok yeni kurallar aldığımız zaman ne olacak? Bizim `process` metodumuz hızlı bir biçimde sürdürülmesi zor bir spagetti kodu canavarına dönüşecektir. Bu kodun iş kurallarımızın değiştiği her sefer değiştirilmesi gerektiğinden, değişikliklere açıktır ve dolayısıyla Açık Kapalı ilkesini ihlal etmektedir. Unutmayın, biz kodumuzun genişletmelere açık olmasını isteriz, değişikliklere değil.
Sipariş doğrulamamızın hepsini doğrudan process metodu içerisinde yapmak yerine, en iyisi yeni bir interface tanımlayalım: OrderValidator:
interface OrderValidatorInterface {
public function validate(Order $order);
}
Ondan sonra, mükerrer siparişlere karşı koruyan bir implementasyon tanımlayalım:
class RecentOrderValidator implements OrderValidatorInterface {
public function __construct(OrderRepository $orders)
{
$this->orders = $orders;
}
public function validate(Order $order)
{
$recent = $this->orders->getRecentOrderCount($order->account);
if ($recent > 0)
{
throw new Exception('Muhtemelen aynı sipariş ikilendi.');
}
}
}
Mükemmel! Şimdi elimizde tek bir iş kuralının küçük, test edilebilir bir enkapsülasyonu var. Şimdi de hesabın askıya alınmış olup olmadığını soruşturan başka bir implementasyon oluşturalım:
class SuspendedAccountValidator implememts OrderValidatorInterface {
public function validate(Order $order)
{
if ($order->account->isSuspended())
{
throw new Exception("Askıya alınmış hesaplar sipariş veremez.")
}
}
}
Şimdi OrderValidatorInterface sözleşmemizin iki farklı implementasyonuna sahibiz, bunları OrderProcessor içinde kullanalım. Yapacağımız tek şey validatorlerden oluşan bir diziyi işlemci olgumuza enjekte etmektir, böylece temel kodumuz geliştikçe doğrulama kurallarını kolaylıkla ekleyip çıkartabileceğiz.
class OrderProcessor {
public function __construct(BillerInterface $biller,
OrderRepository $orders,
array $validators = array())
{
$this->biller = $biller;
$this->orders = $orders;
$this->validators = $validators;
}
}
Sonra, process metodumuzda basitçe validatorleri dolaşabiliriz:
public function process(Order $order)
{
foreach ($this->validators as $validator)
{
$validator->validate($order);
}
// Geçerli siparişi işle...
}
Son olarak, uygulama IoC konteynerinde OrderProcessor sınıfımızı register edeceğiz:
App::bind('OrderProcessor', function()
{
return new OrderProcessor(
App::make('BillerInterface'),
App::make('OrderRepository'),
[
App::make('RecentOrderValidator'),
App::make('SuspendedAccountValidator'),
],
);
});
Mevcut temel kodumuzdan inşa etmek için sadece minimal çaba isteyen bu birkaç değişikliği yapmakla, şimdi mevcut kodumuzda tek satır değişiklik yapmaksızın yeni doğrulama kuralları ekleyip çıkartabiliriz. Her bir yeni validation kuralı OrderValidatorInterfacein sadece yeni bir implementasyonudur ve bu IoC konteynerinde register edilmiştir. Büyük, hantal bir process metodunun unit testini yapmak yerine, şimdi her bir validation kuralını izole olarak test edebiliriz. Kodumuz şimdi genişletmelere açık ama değişikliklere kapalıdır.
Sızdıran Soyutlamalar
İmplementasyon ayrıntılarını sızdıran bağımlılıklar olup olmadığını gözleyin. Bir bağımlılıktaki bir implementasyon değişikliği onun alıcısı sınıfta herhangi bir değişiklik gerektirmemelidir. Alıcı sınıfa değişiklik yapılması gerektiği takdirde, bu bağımlılığın implementasyon ayrıntılarını "sızdırdığı" söylenir. Soyutlamalarınız sızdırıyorsa, Açık Kapalı ilkesi büyük ihtimalle bozulmuştur.
Daha fazla ilerlemeden önce, bu ilkenin bir yasa olmadığını unutmayın. Bu, uygulamanızdaki her kod parçasının "tak-çıkar" olması gerektiğini belirtmiyor. Örneğin, bir MySQL veritabanından birkaç kayıt getiren küçük bir uygulamanın aklınıza gelebilecek her tasarım ilkesine sıkıca yapışması gerekmez. Belki de aşırı tasarlanmış, hantal bir sistem oluşturacağınız için, suçluluk duyarak belli bir tasarım ilkesini körü körüne uygulamayın. Bu tasarım ilkelerinden birçoğunun büyük, muazzam uygulamalardaki sık görülen mimari problemleri çözmek için oluşturulmuş olduğunu aklınızda tutun. Diyebiliriz ki, bu paragrafı, tembel olmak için bir bahane olarak kullanmayın!