fatiherikli

Bu blog post'unda kendi projelerimde ya da çalıştığım şirkette development sürecinde kullandığımız design pattern'ları ve bu konudaki python'un avantajlarına değineceğim.

Design Patterns

Bu kavramın henüz türkçesi bulunmadı :) Genelde tasarım örüntüleri ya da tasarım desenleri denmekte.

Design pattern'lar development sürecinde sıkça rastlanan problemleri çözmek için kullanılan desenlerdir. Bunların bir -desen- olmasının sebebi problemi çözmekten ziyade probleme object-oriented'ın temel felsefelerinden olan reusable (yeniden kullanılabilir) çözümler getirmektir.

Gang of Four (dörtlü çete) olarak bilinen 4 kafadar 1994 yılında Design Patterns: Elements of Reusable Object-Oriented Software isimli kitaplarında 3 farklı kategoride 23 farklı tasarım deseni derlemiştir. Bu kategoriler;

Desen örnekleri vermeden design pattern ve object-oriented prensiplerine göre bir kaç şeyden bahsetmek istiyorum.

Python üzerinde en sık kullanılan 8 pattern'in örneğini vereceğim. Bu 8 pattern'in 8 pattern olmasının sebebi tamamen zannımca, kendi gözlemlerimdir.

Singleton Pattern

Bu desende bir class'ın sadece bir tane instance'ı olması gerekmektedir. En çok kullanılan ve en basit desenlerden biridir. Amacı aynı işi yapan bir sürü instance'ın bellekte ayrı ayrı yer işgal etmesi yerine tek bir instance üzerinden iş yapmasını sağlamaktır. Genellikle database' bağlanmak gibi bir kere yapılması gereken yerlerde kullanılır.

def singleton(klass):
    if not hasattr(klass, 'instance'):
        klass.instance = klass()
    return klass.instance

class Connection(object):
    def __init__(self):
        print "init..." # sadece bir kez calismali.

a = singleton(Connection)
b = singleton(Connection)
print a is b # True

Factory Pattern

Factory pattern'i programın akışına göre belirlediğiniz sınıf ya da objeleri getiren fabrika olarak düşünebilirsiniz. Örneğin kullanılan database türüne göre bir database client'i getirmek için kullanılabilir. Ya da eğer makina linux ise şu modülü, windows işe şu modülü yükle gibi…

Django üzerinde formset'lerle işlem yaparken kullandığımız formset_factory, modelformset_factory birer factory pattern'ına örnektir;

def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
                    can_delete=False, max_num=None):    
    attrs = {   
          'form'      : form,   
          'extra'     : extra,
          'can_order' : can_order,   
          'can_delete': can_delete,
          'max_num'   : max_num  
    }
    return type(form.__name__ + 'FormSet', (formset,), attrs)

Bu desende çağırırken verdiğiniz forma göre ve belirlediğiniz miktara göre form içeren bir FormSet sınıfı döndürülmektedir. Daha da basit bir örnek verecek olursak; tumblr gibi bir servis üzerindeki post tiplerine göre bize model döndüren bir factory method yazalım.

def post_model_factory(model):
    return {
        'dialog' : Dialog,
        'quote'  : Quote,
        'link'   : Link,
        'video'  : Video,
        'audio'  : Audio,
    }.get(model) or Post # eger bulunmazsa, default olarak Post modeli donduruluyor.

Decorator Pattern

Decorator pattern'i bir sınıfa onu türetmeden ya da temel yapısında değişiklik yapmadan çalışma zamanında ek özellikler eklememizi sağlayan bir desendir.

Aslında Singleton Pattern örneğinde bir decorator pattern'i uyguladık. Singleton olmasını istediğimiz sınıf üzerinde birazcık oynama yaptık. Decorator pattern'i yaptığımız işlemin ta kendisidir

def singleton(klass):
    if not hasattr(klass, 'instance'):
        klass.instance = klass()
    return klass.instance

class Connection(object):
    def __init__(self):
        print "init..." # sadece bir kez calismali.

connection = singleton(Connection)

Ayrıca bildiğiniz üzere python'da built-in olarak decorator desteği var. Örneğin singleton pattern örneğini şu şekilde yapabilirdik.

def singleton(klass):
    if not hasattr(klass, 'instance'):
        klass.instance = klass()
    return klass.instance

@singleton # Connection artik bir class degil, instance.  
class Connection(object):
    def __init__(self):
        print "init..." # sadece bir kez calismali.

Proxy Pattern

Proxy Pattern orijinal class'a dokunmadan, başka bir class oluşturup yeni özellikler eklememizi sağlayan bir desendir. Bunun için en iyi örnek olarak django'daki proxy model'leri gösterebilirim.

Django dökümantasyonundan bir örnek vermek istiyorum;

from django.contrib.auth.models import User
class MyUser(User):
    class Meta:
        proxy = True

    def do_something(self):
        pass

Iterator Pattern

Bu pattern python üzerinde built-in olarak gelmektedir. Iterator Pattern bir listedeki elemanların içerisinde dolaşmak, sonraki ve önceki elemanı bulmamızı sağlar. Bu liste sonsuz bir liste olabilir. Örneğin fibonacci serisinin tüm değerlerini bir listeye atamazsınız, ama iterator ile yaptığınızda sadece bir sonraki fibonacci sayısını istediğinizde size o sayıyı hesaplar ve getirir.

En basit şekilde bir iterator deseni;

def iterator():
    yield "hey"
    yield "selam"
    yield "naber"

for item in iterator():
    print item

Bir örnek yapacak olursak; bir model'deki kayıtlar üzerinde hesaplama yapıp sonuçlarından bir liste oluşturmak gerekmekte.

Kötü örnek;

def orders():
    result = []
    for item in Orders.objects.all():
        result.append(item.calculate()) # uzun suren bir islem
    return result

Bu şekilde listenin oluşabilmesi için bütün kayıtların hesaplanması ve listeye eklenmesi gerekiyor. Ama bize sadece ilk 10 kaydın hesaplamaları gerekli. Fail :)

Bunu aşağıdaki gibi yapmamız gerekiyor;

def orders():    
    for item in Orders.objects.all():
        yield item.calculate() # uzun suren bir islem

Ayrıca iterator'lerin aşağıdaki gibi bir kullanımı da mümkündür. Satchmo'nun cart modelinden örnek veriyorum;

class Cart(models.Model):    
    site = models.ForeignKey(Site, verbose_name=_('Site'))
    desc = models.CharField(_("Description"), blank=True, null=True, max_length=10)
    date_time_created = models.DateTimeField(_("Creation Date"))
    customer = models.ForeignKey(Contact, blank=True, null=True, verbose_name=_('Customer'))

    objects = CartManager()

    # her hangi bir sinifa asagidaki gibi iteration ekleyebiliyoruz.
    def __iter__(self):
        return iter(self.cartitem_set.all())  
    # ...

Bu sayede sepete ait ürünleri aşağıdaki kolayca alabiliyoruz.

cart = Cart.objects.from_request(request)  
for item in cart:  
    print item # artik item bir CartItem instance'ı...

Observer Pattern

Publish/subscribe olarakta bilinmektedir. Temel anlamda sınıflarımıza event, yani olay atamamızı sağlayan desenlerdir. Bu desende bir objenin durumunun değişmesi durumunda, dinlemekte olan belirlediğiniz gözlemcilere haber gitmektedir.

Django'daki signal dispatcher kullanımı observer pattern'ine bir örnektir. Django'daki signals kullanımı ilgili bir örnek veriyorum;

def create_user_profile(sender, instance, created, **kwargs):
    if created:
       profile, created = UserProfile.objects.get_or_create(user=instance)
post_save.connect(create_user_profile, sender=User)

Strategy Pattern

Strategy pattern'i bir işin yapılmasının yöntemini çalışma zamanında belirlemenizi sağlar. Benim en sevdiğim desendir :)  Örneğin bir servisiniz için bir importer yazacaksınız diyelim. Yazacağınız sınıfta verileri import ettiğiniz ve nereden import edeleceği kısımlarının ayrı tutulması gerekir. Import ettiğiniz servis bir rss ya da tamamen standart dışı bir xml olabilir. Bu gibi durumlarda komple import servisiniz yerine, import edeceğiniz yere özel strategy sınıfı düzenleyip o şekilde import etmeniz gerekir.

Pika'nın connection sınıfında bu deseni görebiliriz.

# ReconnectionStrategy super sinifi bos bir interface sinifidir.  
class NullReconnectionStrategy(ReconnectionStrategy):
    pass

class SimpleReconnectionStrategy(ReconnectionStrategy):
    can_reconnect = True
    def __init__(self, initial_retry_delay=1.0, multiplier=2.0,
                 max_delay=30.0, jitter=0.5):       
        # implement edilen yerler onemli degil, kirptim.

class Connection(object):
    def __init__(self, parameters=None, on_open_callback=None,
                 reconnection_strategy=None):  
        # ...       
        self.reconnection = reconnection_strategy or NullReconnectionStrategy()  
        # ...

Connection sinifinda re-connection işleminin strategy olarak alındığını görüyoruz. Burada yapılan işlemin adı composition'dur. Connection sınıfında re-connection işlemlerinin yapılması yerine re-connection işlemi yapılacak yerlerde buradaki tanım üzerinden yapılacaktır.

Ayrıca zorunda olmadıkça buradaki gibi uzun uzun strategy tanımları yapmamıza gerek yok. Zaten python'daki fonksiyonlar first-class objelerdir. Birazcık pitonik olmak lazım :) Aşağıdaki örnekle daha iyi anlaşılacağını umuyorum.

def urllib_strategy(url):
    import urllib2
    return urllib2.urlopen(url).read()

def requests_strategy(url):
    import requests
    return requests.get(url).content

def get_html_source(url, opener_strategy=urllib_strategy): # default urllib_strategy
    return opener_strategy(url)

print get_html_source("http://fatiherikli.com")
print get_html_source("http://fatiherikli.com",
                      opener_strategy=requests_strategy)
print get_html_source("http://fatiherikli.com",
                      opener_strategy=lambda url : "fake html source.")

Bu deseni çoğu yerde görebilirsiniz. Bir örnek daha; Django'daki User application'ının view'larındaki login view'ında bu pattern uygulanmış durumda.

def login(request, template_name='registration/login.html',
          redirect_field_name=REDIRECT_FIELD_NAME,
          authentication_form=AuthenticationForm):
    redirect_to = request.REQUEST.get(redirect_field_name, '')
    form = authentication_form(data=request.POST)  
    # ...

Gördüğünüz üzere AuthenticationForm 'u view'da parametre olarak almaktadır. Varsayılan olarak kendi formunu kullanıyor, ancak siz view'ı çağırırken urls.py'nizde kendi login formunuzu verebilmektesiniz.

Null Object Pattern

Null object pattern'i olmayan bir obje üzerinde işlem yaparken programın hata vermemesini sağlamaktır. Ayrıca bu desen sayesinde gereksiz null kontrollerinden kurtulmuş oluyoruz.

Bunun örneğini yine django üzerinden vereceğim. Bildiğiniz üzere django bize request.user dediğimizde bize aşağıdaki User sınıfını döndürmekte;

class User(models.Model)  
    # ...  
    def is_anonymous(self):  
        return False

    def is_authenticated(self):
        return True  
   # ...

Gördüğünüz üzere User sınıfında is_authenticated ve is_anonymous method'ları direk true-false döndürecek şekilde implement edilmiş. Bunun sebebi eğer kullanıcı login olmuşsa bu sınıf kullanılacak, login olmamış ise aşağıdaki AnonymousUser sınfı kullanılmasıdır. AnonymousUser sınıfında is_anonymous ve is_authenticated methodları tam ders durumda olacaktır.

class AnonymousUser(object):
    id = None
    username = ''
    is_staff = False
    is_active = False
    is_superuser = False      
    # ...
    def __unicode__(self):
        return 'AnonymousUser'

    def is_anonymous(self):
        return True

    def is_authenticated(self):
        return False  
    # ...

Gördüğünüz gibi bu sınıfta bir model instance'ı taklit edilmiş durumda. Bu sayede request.user.is_authenticated dediğimizde None type bla bla hatasını almadan kullanıcının login olup olmadığını anlayabiliyoruz.

Ayrıca bu desenin örneğini strategy pattern'inin örneklerinde verdiğim pika kütüphanesinin NullReconnectionStrategy sınıfında da görebilirsiniz.

Bu desenin başka bir örneğini satchmo'nun cart modelinde görebilirsiniz.

class NullCart(object):
    desc = None
    date_time_created = None
    customer = None
    total = Decimal("0")
    numItems = 0

    def add_item(self, *args, **kwargs):
        pass

    def remove_item(self, *args, **kwargs):
        pass

    def empty(self):
        pass

    def __str__(self):
        return "NullCart (empty)"

    def __iter__(self):
        return iter([])

    def __len__(self):
        return 0

    def save(self):
        pass

Bağlantılar

[http://en.wikipedia.org/wiki/Software_design_pattern](http://en.wikipedia.or g/wiki/Software_design_pattern)

[http://www.python.org/workshops/1997-10/proceedings/savikko.html](http://www .python.org/workshops/1997-10/proceedings/savikko.html)

[http://sourcemaking.com/design_patterns](http://sourcemaking.com/design_patt erns)

[http://www.javacamp.org/designPattern/](http://www.javacamp.org/designPatter n/)

[http://c2.com/cgi/wiki?DesignPatterns](http://c2.com/cgi/wiki?DesignPatterns )

[http://www.blackwasp.co.uk/GofPatterns.aspx](http://www.blackwasp.co.uk/GofP atterns.aspx)

Ayrıca türkçe olarak python üzerinde design pattern'lerle ilgili Yaşar Arabacı'nın bir yazısı bulunmakta;

[http://yasararabaci.tumblr.com/post/14873752371](http://yasararabaci.tumblr. com/post/14873752371)

Sunumlar

[http://www.slideshare.net/guest46da5428/application- of-…](http://www.slideshare.net/guest46da5428/application- of-...)

[http://www.slideshare.net/saurabh.net/design- patterns…](http://www.slideshare.net/saurabh.net/design- patterns...)

comments powered by Disqus