Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Marker Interface Pattern(İşaretleyici Arayüz Tasarımı)

Merhaba,

Bu içeriğimizde nesneler hakkında ek bilgi sağlayacak olan Marker Interface Pattern(İşaretleyici Arayüz Tasarımı)‘ı inceliyor olacağız.

Marker Interface Pattern; kod yazma süreçlerinde derleyicinin nesneler hakkında ek bilgilere sahip olabilmesini ve böylece ilgili nesnenin kullanılacağı noktaları derleme sürecinde kurallar eşliğinde belirleyerek, kodu runtime’da olası hatalardan arındırmamızı sağlayan bir pattern’dır.

Özünde, belirli görevlere istinaden belirli arayüzlerle ya da class’larla işaretlenmiş nesneleri devreye sokabilmemizi sağlar ve böylece bu işaretlenmiş nesnelerin dışında o görevlerde farklı türlerde nesnelerin kullanılmasına müsaade etmeyerek kodu daha kontrollü hale getirebilme imkanı tanıyaran bir stratejik manevra oluşturur.

Marker Interface Pattern(İşaretleyici Arayüz Tasarımı)

Bu görsel Marker Interface Pattern’ın davranışını en açık şekilde yansıtmaktadır. Dikkat ederseniz eğer, yeşil büyük halkadan sadece yeşil renkteki objelerin geçebileceği ve diğerlerine izin verilmeyeceği ifade edilmiştir. İşte bu mantığı yazılımda da objelerimiz için uygulamaya Marker Interface Pattern denmektedir.

Konuya dair bir örnek vermemiz gerekirse eğer; birçok entity’den meydana gelen bir veritabanındaki silinebilir tabloları(ya da nesneleri) diğerlerine nazaran ifade edebilmek için Marker Interface Pattern kullanılabilir. Bunun için yapılması gereken aşağıdaki gibi işaretleyici mahiyette bir arayüzün oluşturulmasıdır.

    public interface IErasable { }

Bu arayüz sadece belirli bir işlem için seçilmiş olan nesneleri işaretlemek yani o işleme özel olarak kullanılabilir olduklarını ifade etmek için tasarlanmaktadır. Dolayısıyla bu tarz işaretleyici mahiyetteki arayüzlerin içerisi genellikle boş olarak tanımlanmaktadır.

Şimdi yapılması gereken veritabanından silinebilir olan entity’leri ilgili ‘IErasable’ arayüzüyle işaretlemektir.

    public class Entity1 : IErasable { ... }
    public class Entity2 : IErasable { ... }

Yukarıdaki ‘Entity1’ ve ‘Entity2’ nesneleri veritabanından silinebilirliği ifade ederken,

    public class Entity3 { ... }

‘Entity3’ nesnesi ‘IErasable’ ile işaretlenmediği için bu türdeki veriler veritabanından silinemezler!

Dolayısıyla yukarıdaki üç sınıfa da gözle bakıldığında hangisinin silinebilir olup olmadığı rahatlıkla görülebilmektedir ve bunun yanında iş mantığı açısından da bu kural koyularak runtime’da olası hatalar ortadan kaldırılmaktadır. Tabi ki de bu nesneleri kullanan bir mimari tasarlanırken ‘IErasable’ arayüzüne göre bir constraint’in oluşturulması bir iş kuralı gereğidir.

Bir başka örnek vermemiz gerekirse eğer yukarıdakine benzer olarak entity’lerin tanımlanmasıdır. Bir uygulamada onlarca sınıf tasarımı mevcut olabilir. Bu sınıflardan hangilerinin bir entity olduğunu ifade edebilmek için Marker Interface Pattern’ı kullanabiliriz.

    public interface IEntity { }

Yukarıdaki gibi tasarlanmış olan bir işaretleyici arayüz ile uygulamadaki tüm entity sınıflarını aşağıdaki gibi işaretleyebiliriz.

    public class Employee : IEntity { ... }
    public class Product : IEntity { ... }
    public class Order : IEntity { ... }

Böylece bu uygulamada hangi sınıfların bir entity olarak kullanıldığını rahatlıkla görebilmekteyiz. Tabi bunun dışında genellikle kullanmayı tercih ettiğimiz Repository pattern’ını tasarlarken inşa edilen generic yapıda yanlışlıkla entity olmayan bir sınıfın kullanılmasını da constraint’ler sayesinde aşağıdaki gibi rahatlıkla engelleyebilmekteyiz.

    public interface IRepository<T> where T : class, IEntity
    {
        IQueryable<T> Get();
        IQueryable<T> Get<A>() where A : class, IEntity;
        IQueryable<T> GetWhere(Expression<Func<T, bool>> metot);
        IQueryable<A> GetWhere<A>(Expression<Func<A, bool>> metot) where A : class, IEntity;
    }

Yukarıdaki örnek kod bloğunda görüldüğü gibi ilgili Repository tasarımında generic olarak tanımlanan T ve A parametrelerine ‘IEntity’ türünden bir nesnenin gelebileceği bildirilmiş ve diğerleri reddedilerek derleyici hatası verilmiştir.

Son olarak konuyu daha da net anlamlandırabilmek için bir şirketteki personellerin güvenilirlik durumlarını Marker pattern ile ifade eden aşağıdaki misali sunalım. Şöyle ki;

    public interface ISafe { }

şeklinde bir işaretleyici tanımlayarak,

    public class AdministrativePersonel : ISafe { }
    public class Person { }

yukarıda görüldüğü gibi tüm personellerden ziyade(Person) sadece idari personel olan ‘AdministrativePersonel’ nesnelerini ‘ISafe’ ile işaretlendirerek güvenilir olduklarını ifade edebiliriz.

Marker(İşaretleyici) Interface Pattern’ın diğer bir adı da etiketleyici mahiyetinde Tagging Interface Pattern’dır.

Annotations’lar

Marker pattern’ı uygularken annotations’ları da kullanabiliriz. Özellikle C# programlama dilinde annotations’lar Attribute olarak kullanılmaktadırlar. Misal olarak yukarıdaki örneklerden ilki olan veritabanındaki silinebilir tabloları(ya da nesneleri) attribute’lar ile ifade edebilmek için aşağıdaki gibi çalışmamız yeterlidir.

    [AttributeUsage(AttributeTargets.Class)]
    public class ErasableAttribute : Attribute { }

İlk olarak işaretleme maksatlı bir attribute oluşturulmaktadır. Şimdi bu attribute ile entity sınıflarını aşağıdaki gibi işaretleyerek hangisinin silinebilir özellikte olduğunu ifade edelim.

    [Erasable]
    public class Entity1 { ... }
    [Erasable]
    public class Entity2 { ... }
    public class Entity3 { ... }

Evet, görüldüğü üzere [Erasable] ile işaretlenen sınıflar bizler için zahiren silinebilir entity’lere karşılık gelmektedir. Peki zahiren silinebilir olan bu sınıfların programatik açıdan silinebilir olup olmadığını nereden anlayacağız? diye sorduğunuzu duyar gibiyim… Nihayetinde interface ile yapılan çalışmalarda polimorfizm’den faydalanabilmekte ya da sınıf tasarımlarında belli başlı constraint’ler uygulayarak rahatlıkla ilgili nesnenin işaretlenip işaretlenmediği ayrımını sağlayarak, anlayabilmekteydik. Amma velakin söz konusu yapılar attribute’lar ise reflection’a girerek hareket etmemiz gerekmektedir.

        static bool IsErasable<T>(T instance) where T : class
            => typeof(T).CustomAttributes
                   .Any(attribute => attribute.AttributeType == typeof(ErasableAttribute));

Yukarıdaki kod bloğunu incelerseniz gelen parametrenin türüne generic olarak bürünen T parametresinin bir ErasableAttribute türünden attribute ile işaretlenip işaretlenmediği sorgulanmakta ve geriye bool türünden değer döndürülmektedir. Bu kontrolü benzer mantıkla aşağıdaki gibi de gerçekleştirebilirsiniz;

        static bool IsErasable<T>(T instance) where T : class
            => typeof(T).IsDefined(typeof(ErasableAttribute), true);

Velhasıl bu tarz bir reflection kontrolü ile ilgili sınıfın işaretli olup olmadığını öğrendikten sonra gerisi iş kuralları gereği gerekli kontroller eşliğinde tasarlanabilir ve uymayan bir sınıf söz konusuysa hata fırlatma gibi bir refleks gösterilerek programın akışı değiştirilebilir. Artık burası size kalmış 🙂

Nihayetinde;

Marker pattern’ı derleme zamanında uygulamak istiyorsak interface’i, yok runtime’da uygulamak istiyorsak eğer attribute’ları tercih etmemiz gerekmektedir.

Performans açısından :
Marker attribute’ları reflection kullanılacağından dolayı Marker interface’lere nazaran daha yavaş olacaktırlar. Bundan dolayı instance is type kontrolü reflection kullanan type.IsDefined(typeof(...Attribute...) kontrolüne nazaran daha performanslıdır.

Uygulama Domain’inde İşaretlenmiş Nesneleri Reflection İle Yakalamak

Kâh marker interface olsun, kâh marker attribute olsun uygulama domaini genelinde işaretlenmiş sınıfları yakalayabilmek ve elde edebilmek için aşağıdaki algoritmaları kullanabilirsiniz.

Attribute için :

            AppDomain.CurrentDomain.GetAssemblies()
                      .SelectMany(s => s.GetTypes())
                      .Where(t => t.CustomAttributes.Any(attribute => attribute.AttributeType == typeof(...Attribute...) && t.IsClass))
                      .ToList();

Interface için :

            AppDomain.CurrentDomain.GetAssemblies()
                      .SelectMany(s => s.GetTypes())
                      .Where(t => typeof(...Interface...).IsAssignableFrom(t) && t.IsClass))
                      .ToList();

Nihayetinde özellikle Asp.NET Core gibi modern mimarilerde geliştirilmekte olan birçok library artık belli interface’lerle işaretlenmiş sınıfları tek bir tanımlamayla domain altında arayıp bulmakta ve işlemlerini gerçekleştirmektedir. (bknz : MediatR, AutoMapper, Fluent Validation vs.) O yüzden bu yöntemler kütüphane geliştirenler için oldukça kritik arz edebilir.

Son Dedikodular
Marker interface, özünde normal bir interface/arayüz değildir. Çünkü interface/arayüz teriminin -bir interface, o interface’i uygulayan tüm sınıflara içindeki imzaları uygulatan kontrattır/imzadır- şeklinde olan açıklamasından yola çıkarsak eğer interface’in uygulandığı sınıfın ‘Ne’ olduğunu, o interface’i uygulayan sınıfın ise ‘Nasıl’ olduğunu ifade ettiğini görürüz. İşte bu mantıkla Marker interface’e baktığımızda bunun normal bir interface’den ziyade sadece işaretleme sürecinde amaca hizmet eden ama özünde varlık sebebinden çok farklı kullanılan bir yapı olduğunu görmekteyiz.

Yani bir interface, implementer(uygulayıcı) ile consumer(tüketici) arasındaki sözleşmeye karşılık gelirken Market interface bambaşka bir anlama gelir. İşaretler. Sadece bir nitelik kazandırır…

Kimi uzmanlar Marker Interface Pattern’ın bir Design Pattern olmadığı görüşündedir!

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

1 Cevap

  1. softwaredeveloper dedi ki:

    Yine çok güzel bir paylaşım olmuş. Paylaşımınız için teşekkür ederim.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir