GRASP İlkeleri

grasp-ilkeleri

Nesne yönelimli yazılım geliştirmede kullanılan, sorumlulukların nasıl atanması gerektiğiyle ilgili bir tasarım kalıbıdır. Bir sınıf veya metodun sorumluklarının atanmasında ve yönetilmesinde, bu sınıf veya metodların etkileşimlerinde ki ilkeleri belirlemişler. Wiki.

9 adet uygulanabilecek ilkesi bulunmaktadır.

Information Expert(Bilgi uzmanı)

Bilginin asıl sahibine sorumluluğu vermek olarak özetlenebilir. Örneğin Order nesnesi OrderItem nesnelerine ait tüm bilgileri içerdiği bir tasarımda. Order‘a ait toplam tutarı OrderItem nesnelerine sahip olduğundan dolayı kendisi hesaplamalıdır. Tabi bu her zaman geçerli olan bir yöntem değil şöyle ki; Mail göndereceksiniz ve tüm mail içeriğine içeren bir objeye sahipsiniz. Bu durumda obje kendi kendisini göndermesi bir miktar saçma olacaktır. Aslında burada eylem mail göndermek yani Notification objesi mail gönderebilmek için gerekli olan tüm parametrelere sahip. Mail objesini Notification sınıfına parametre olarak göndererek eylemi gerçekleştirecek olan sınıfın işini yapması sağlanmalı. Özetle eylemi gerçekleştirecek olan objenin o eylem hakkında bilgi sahibi olması gerekiyor. Eğer bilgi sahibi değilse zorlamanın anlamı yok. Bir bilgi sahibi sınıf tasarlayın.

class Order
{
    private ?int $id = null;
    .
    .
    .
    private ArrayCollection $orderItems;

    public function calculateTotalAmount()
    {
        $totalAmount = 0;
        /** @var OrderItem $orderItem */
        foreach ($this->orderItems as $orderItem){
            $totalAmount += $orderItem->getAmount();
        }
        return $totalAmount;
    }
}

Creator(Yaratıcı)

Nesneyi kim yaratıyor sorusunun cevabını verebildiğiniz objedir aslında. B objesi A objesine ait bilgiye sahip veya onu kullanıyor ise yada B objesi A yı oluşturabilmek için gerekli başlangıç verilerine sahip ise B objesi Creator konumundadır. Burada Factory ve Prototype gibi yöntemler uygulanabilir. Bir önceki örnekte OrderItem objelerini oluşturma işini Cart objesine atayabiliriz.

Controller(Denetleyici)

Sürecin nasıl başlayacağını, kimin kontrol ve koordinasyonu sağlayacağının belirleyecek olan sınıflardır.

Mediator uygun bir yöntem olarak uygulanabilir. MVC deki Controller sınıfları bu görevi üstlenebilir. İsteğin geldiği ve bu isteğin karşılığında neler yapılacağını ve nasıl bir cevap döneceğini yönetir. Basit ve temel bir görevi vardır yerine getirilmesi gereken bir dizi görevin yerine getirilmesini sağlamak için diğer nesnelerin koordinasyonu.

Indirection(Dolaylı)

Birden fazla nesneler arasında ki doğrudan bağlantıdan kaçınmak için kullanılan yöntem. Neden? Bağımlılığı azaltmak.

Şöyle ki: Ürünleri çektiğiniz bir ProductService sınıfına sahipsiniz ve içerisinde ProductApi adında bir sınıfı kullanarak ürünlerinizi listeliyorsunuz.

class ProductService
{
    private ProductApi $productApi;

    public function __construct(ProductApi $productApi)
    {
        $this->productApi = $productApi;
    }

    public function getProducts($query)
    {
        return $this->productApi->get($query);
    }
}

Burada şu an için bir problem yok gibi görünebilir ilerleyen zamanlarda ProductApi değiştirilmek istendiğinde ProductService’e başka bir API sınıfını kullanarak hayatınıza devam edebilirsiniz. Fakat ya bu sadece FooApi adında içerisinde başka başka servislere erişebilen bir sınıf olsaydı ve bir çok başka sınıf bunu kullansaydı? Evet IDE ile Find And Replace yapardınız. Şakaydı.

Bugün için kullandığınız database veya yukarıda ki örnekte olduğu API isteklerini gerçekleştiren sınıfı bir arayüz üzerinden üretmemiz gerekiyor çünkü ileride değiştirmek istediğimizde arayüze uygun geliştirme yaparak bu işin altından kolayca kalkabiliriz. Database örneği en basit ve anlaşılır örnek diye düşünüyorum. Bir ORMManagerInterface‘e sahip olduğumuzu düşünelim ve Mysql bağlantısı gerçekleştirmiş olsun ve bir gün artık Mysql değil de MongoDB kullanacaksınız. Projenin her yerinde bulunan ORMManagerInterface’i kullanarak hangi veritabanı bağlantısını kullandığını önemsemeden işinizi yapmaya devam edebilirsiniz.

Mediator,Adapter,Bridge bu yöntem için kullanılabilecek güzel desenlerdir.

Low Coupling(Düşük Bağlantı)

En temel ve en gerekli olduğunu düşündüğüm yöntemdir. Değişimin etkisini olabildiğince azaltmayı ve en az bağımlılığı hedefler.

Bağlantı bir nesnenin bir diğeri ile nasıl bir ilişkisi olduğu ölçüsüdür. Nesneler arasında ki iletişim olabildiğince izole olmalı. Indirection ile düşük bağlantı sağlanabilir. Birbirine bağlı bir sınıflar dizesi düşünün, birbirlerine olan bağlantı ne kadar katı ve fazla olursa o yapıda bir sınıfı değiştirmek beklenmedik bir çok soruna yol açabilir ayrıca çok dertli bir işlem olacaktır.

Elbet de birbirine bağımlı yapılarımız olacaktır. Fakat bağımlılık ne kadar az olursa sistemimizin kararlılığı fazla olacaktır. Creator yöntemi aslında Low Coupling‘i sağlamak için güzeldir. Sadece Creator‘u kullanarak ilgili nesneleri gerektiği yerlerde oluşturmak çok az bağımlılığımız olmasını sağlayacaktır.

High Cohesion(Yüksek Uyum)

Single Responsibility(Solid’in S’si) çok benzeyen bir yöntem. Bir sınıf kendisine tanımlanmış bir işi yapmalı. Sınıflar arası veya bir sınıf içerisinde ki metodların uyumları ile ilgilidir. Customer ile Order arasında bir bir satın alma ilişkisi mevcut yani Customer sınıfı kendisine ait Order nesnelerine erişebilir burada mantıksız bir durum yok.

Çok sık kullanılan bir örnek olarak Notification sınıfının birden fazla kanalı kullanarak bildirim gönderdiğini düşünelim.

class Notification
{
    public static function sendEmail($args)
    {
    }

    public static function sendSMS($args)
    {
    }
}

Ne güzel tek bir sınıf üzerinden statik metodlar aracılığıyla istediğim kanal üzerinden bildirim gönderebiliyorum.Tabi bu yapıda bile bu iş bu kadar kolay değil ama öyleymiş gibi yapalım. Burada tek sorumluluk ilkesinden oldukça uzak olduğumuz gibi sınıfın yaptığı işler arasında bir tutarsızlık mevcut. Email mi yoksa SMS mi göndermeli? Evet Notification diyince her ikisinide yapsa ne olacak gibi anlam çıkabilir fakat ilerleyen zamanlarda yeni kanallar üzerinden bildirim göndermek gerektiğinde Notification sınıfınız bir God Class a dönüşecek. Ayrıca her bir kanal için bildirim göndermenin farklı farklı prosedürleri bulunuyor ve mantıksal olarak bir sınıfın birden fazla kanaldan sorumlu olmasının karmaşasına ihtiyacımız yok.

Basit çözümlerden biri her bir kanal için ayrı bir sınıf oluşturmak olabilir. EmailNotification SMSNotification gibi sınıflar oluşturarak bu sınıfların kendi iç süreçlerini yönetmek ve yürütmekten sorumlu olması sağlanabilir.

Polymorphism(Polimorfizim)

Değişen dinamik yapılarda sorumlulukların nasıl bağlandığını belirler. Alternatif yapıları yönetmeyi ve uygulamayı hedefler. Burada Strategy Pattern uygulanabilir. Bir ödeme alt yapısına sahipsiniz ve müşterilerinize bir alternatif daha sunmak istiyorsunuz. Bu durumda kullanabileceğiniz yöntemlerden biri bu. Sorumlulukları if else ile ayırmadan yönetebilmektir aslında.

Pure Fabrication(Saf Üretim)

Her zaman sorumluluk bir nesneyi ifade etmeyebilir. Bu durumda yeni bir arayüz ve sınıf oluşturmak en doğru yaklaşım olarak söylenebilir. Aslında soyut bir şeyi somut bir şeye uydurmak anlamına geliyor. Yüksek uyum, düşük bağlantıyı ve yeniden kullanılabilirliği desteklemesi yeterlidir.

Protected Variations(Korumalı Varyasyonlar)

Aslında tüm hepsinin özeti şeklinde düşünülebilir. Yeni bir şey eklemek veya değiştirmek var olan yapımızı ve işlevlerimizi bozmamalıdır. Yeni bir sınıf oluşturduğum için başka sınıfların işlevlerinde değişiklik olmamalı.

İstikrarsız ve değişime açık olan nesneleri bir arabirim ile tanımlamak gerektiğini söyler. Bu sayede nesnelerin değişimlerinin sistemin geri kalanını etkilememesi sağlanır.