23.02.2010

Паттерны программирования в питоне

Как известно впервые концепция паттернов программирования (проектирования) была опубликована "Бандой четырех" (Erich Gamma, Richard Helm, Ralph Johnson, Jon Vlissides; 1995). В ней они описали стандартные шаблоны проектирования программного кода, с которыми так или иначе сталкивается каждый разработчик. Это было сделано с целью, решить проблему "изобретения велосипедов" в программном коде, а также стандартизировать терминологию используемую разработчиками в общении. По моему скромному мнению, часть этих шаблонов узкоспециализирована и относится непоседственно к типизированным языкам C++ и Java, на примере которых и строилась вся концепция. Например паттерн Singleton предназначен для описания класса имеющего в любой момент времени единственный объект реализацию и доступный всем компонентам программы. В python уже есть модули, которые и являются по факту Singleton. Пишем модуль singleton.py и в любом месте программы делаем import syngleton. Вот, всегда единственный инстанс (sys.modules[наш модуль]), всегда доступен, никаких дополнительных телодвижений. Или паттерны Iterators и Generators, присутствующие в python изначально.

Другое дело например паттерн Observer. Он относится к паттернам поведения и реализует обработку событий. Реализуется связь один ко многим, между объектом и зависимыми от него компонентами. При изменении состояния объекта, все зависимые компоненты оповещаются и изменяются автоматически.

Вот простейшая реализация этого шаблона:

class Subject:
    def __init__(self):
        self._observers = []

    def attach(self, observer):
        if not observer in self._observers:
            self._observers.append(observer)

    def detach(self, observer):
        try:
            self._observers.remove(observer)
        except ValueError:
            pass

    def notify(self, modifier=None):
        for observer in self._observers:
            if modifier != observer:
                observer.update(self)


# Example usage
class Data(Subject):
    def __init__(self, name=''):
        Subject.__init__(self)
        self.name = name
        self.data = 0

    def setData(self, data):
        self.data = data
        self.notify()

    def getData(self):
        return self.data


class HexViewer:
    def update(self, subject):
        print 'HexViewer: Subject %s has data 0x%x' % (subject.name, subject.getData())


class DecimalViewer:
    def update(self, subject):
        print 'DecimalViewer: Subject %s has data %d' % (subject.name, subject.getData())


# Example usage...
def main():
    data1 = Data('Data 1')
    data2 = Data('Data 2')
    view1 = DecimalViewer()
    view2 = HexViewer()
    data1.attach(view1)
    data1.attach(view2)
    data2.attach(view2)
    data2.attach(view1)

    print "Setting Data 1 = 10"
    data1.setData(10)
    print "Setting Data 2 = 15"
    data2.setData(15)
    print "Setting Data 1 = 3"
    data1.setData(3)
    print "Setting Data 2 = 5"
    data2.setData(5)
    print "Detach HexViewer from data1 and data2."
    data1.detach(view2)
    data2.detach(view2)
    print "Setting Data 1 = 10"
    data1.setData(10)
    print "Setting Data 2 = 15"
    data2.setData(15)

if __name__ == '__main__':
    main()

В примере в принципе все понятно из кода. Мы заводим специальный класс способный подписывать, отписывать и оповещать наблюдателей и наследуем от него основной класс. У наблюдателей соответственно должны присутствовать специальные интерфейсы для работы с событиями.

Вот пример посложнее:

Допустим у нас есть объект-таймер, каждую секунду оповещающий подписанных на него наблюдателей о своем изменении. Код класса этого объекта может выглядеть так:

class ClockTimer:
        def GetHour(self):
                return self._hour
        def GetMinute(self):
                return self._minute
        def GetSecond(self):
                return self._second

        TickEvent = Event()
        def OnTick(self):
                ClockTimer.TickEvent.call(self, self.GetHour(),
                        self.GetMinute(), self.GetSecond())

        def Tick(self):
                # update internal time-keeping state
                # ...
                self.OnTick()

Каждую секунду вызывается метод onTick который должен информировать наблюдателей о изменениях. Теперь мы можем создать наблюдателя, например класс цифровых часов, который подпишется на изменения таймера:

class DigitalClock(Widget):
        def __init__(self, clockTimer):
                self.__subject = clockTimer
                clockTimer.TickEvent += self.Update

        def close(self):
                self.__subject.TickEvent -= self.Update

        def Update(self, subject, hour, min, sec):
                self.displayedTime = (hour, min, sec)
                self.Draw()

        def Draw(self):
                # draw the digital clock

Создадим вспомогательный класс Delegate, способный манипулировать коллекцией методов или функций, мы же не знаем сколько наблюдателей подпишутся на изменения.

class Delegate:
    '''Handles a list of methods and functions
    Usage:
        d = Delegate()
        d += function    # Add function to end of delegate list
        d(*args, **kw)   # Call all functions, returns a list of results
        d -= function    # Removes last matching function from list
        d -= object      # Removes all methods of object from list
    '''
    def __init__(self):
        self.__delegates = []

    def __iadd__(self, callback):
        self.__delegates.append(callback)
        return self

    def __isub__(self, callback):
        # If callback is a class instance,
        # remove all callbacks for that instance
        self.__delegates = [ cb
            for cb in self.__delegates
                if getattr(cb, 'im_self', None) != callback]

        # If callback is callable, remove the last
        # matching callback
        if callable(callback):
            for i in range(len(self.__delegates)-1, -1, -1):
                if self.__delegates[i] == callback:
                    del self.__delegates[i]
                    return self
        return self

    def __call__(self, *args, **kw):
        return [ callback(*args, **kw)
            for callback in self.__delegates]

Класс способен принять функцию, удалить функцию и вызвать все функции по порядку. И наконец описываем класс события.

class Event(property):
    '''Class event notifier
    Usage:
        class C:
            TheEvent = Event()
            def OnTheEvent(self):
                self.TheEvent(self, context)

        instance = C()
        instance.TheEvent += callback
        instance.OnTheEvent()
        instance.TheEvent -= callback
    '''
    def __init__(self):
        self.attrName = attrName = "__Event_" + str(id(self))
        def getEvent(subject):
            if not hasattr(subject, attrName): 
                setattr(subject, attrName, Delegate())
            return getattr(subject, attrName)
        super(Event, self).__init__(getEvent)

    def call(self, subject, *args, **kw):
        if hasattr(subject, self.attrName):
            getattr(subject, self.attrName)(subject, *args, **kw)

Самое сложное разобраться в классе Event. Его основная задача создать объект Delegate не сразу, а только при необходимости. Итак в DigitalClock мы подписываемся на событие TickEvent, а в ClockTimer зажигаем это событие.

Комментариев нет:

Отправить комментарий