Хочу поділитись своїми думками з приводу одного прикольного стилю програмування класів - ланцюгові виклики. Для когось це буде не новим, комусь, може, не подобається, але я вважаю, що такому стилю можна знайти застосування, при цьому код програми буде виглядати більш зрозумілим і логічним.
Цей стиль буде зручним для класівв моделей даних, методи яких містять деяку логіку, що змінює стан класу, наприклад, класи об'єктів в комп'ютерних іграх, абстрактні моделі в моделюючих системах різного плану.
Проста задачка
Розглянемо реалізацію простого класу, назвемо його "тупий охоронець". Уявимо собі, що ми робимо комп'ютерну гру. У нас є замок, а біля воріт патрулює охоронець: ходить туди-сюди, більше нічого не робить. Код на Python 3.1:class StupidGuard:
"""
Stupid guard that moves around given route (way points).
"""
direction = 0
position = {'x': 0, 'y': 0}
def go(self, steps):
self.position['x'] += steps * cos(radians(self.direction))
self.position['y'] += steps * sin(radians(self.direction))
def rotate(self, angle):
self.direction += angle
def __repr__(self):
return 'Guard: %s' % self.position
Код і математика дуже прості і, думаю, роз'яснень не вимагається. Отже, охоронець у нас є, давайте примусимо його патрулювати:
Код і математика дуже прості і, думаю, роз'яснень не вимагається. Отже, охоронець у нас є, давайте примусимо його патрулювати:
guard = StupidGuard()
print (guard)
guard.go(5)
guard.rotate(180)
guard.go(5)
guard.rotate(180)
print(guard)
print(guard)
Ось, що нам каже Python про стан нашої людини на початку і в кінці шляху:
Guard: {'y': 0, 'x': 0}
Guard: {'y': 6.123233995736766e-16, 'x': 0.0}
Отже, задачка виконана - охоронець ходить. Тільки довго якось ходить - 4 рядки коду. А хочеться, щоб він ходив не 4 рядки коду, а, наприклад, одну :)
Ланцюгові виклики
Це, в принципі, і є той простий спосіб - ланцюгові виклики методів. Суть в тому, що наведений код переписати так:
Неважко здогадатись, що для цього потрібно всього навсього зробити так, що методи go і rotate повертали self, а не None, як зараз. Тобто ці методи перетворюються в такі:
def go(self, steps):
self.position['x'] += steps * cos(radians(self.direction))
self.position['y'] += steps * sin(radians(self.direction))
return self
def rotate(self, angle):
self.direction += angle
return self
Працює? Прекрасно працює. Але такий спосіб мені здався недостатньо гламурним і я вирішив зробити декоратор, назвемо його Chained, щоб кожен раз не повертати self. Звісно, цей код не скорочує, але виразність такого коду набагато вища, ніж return True. Декоратор-декоратором, а просто декоратором обійтись не вийде. Оскільки декоратор методу повинен мати доступ до экземпляра класу, - доводиться писати дескриптор.
Пишемо дескриптор
Отже, дескриптор для цієї задачі досить простий і невеликий:
class Chained:
"""
Descriptor makes method to always return its owner class instance
"""
def __init__(self, method):
self.method = method
def __get__(self, instance, owner):
def wrapper(*args, **kwargs):
self.method(instance, *args, **kwargs)
return instance
return wrapper
Все, що він робить - огортає метод екземпляра в функцію, яка передає початковий код на виконання, але повертає екземляр класу, метод якого мы декоруємо. Відповідно, зміниться код нашого охоронця:
class StupidGuard:
"""
Stupid guard that moves around given route (way points).
"""
direction = 0
position = {'x': 0, 'y': 0}
@Chained
def go(self, steps):
self.position['x'] += steps * cos(radians(self.direction))
self.position['y'] += steps * sin(radians(self.direction))
@Chained
def rotate(self, angle):
self.direction += angle
def __repr__(self):
return 'Guard: %s' % self.position
Тепер ми з легкістю можемо примусити його ходити вже двомя способами:
guard = StupidGuard()
print (guard)
guard.go(5)
guard.rotate(180)
guard.go(5)
guard.rotate(180)
print(guard)
guard.go(5).rotate(180).go(5).rotate(180)
print(guard)
guard.go(5).rotate(180).go(5).rotate(180)
print(guard)
Ось що інтерпретатор повідомляє про стан об'єкта:
Guard: {'y': 0, 'x': 0}
Guard: {'y': 6.123233995736766e-16, 'x': 0.0}
Guard: {'y': 1.224646799147353e-15, 'x': 0.0}
Guard: {'y': 6.123233995736766e-16, 'x': 0.0}
Guard: {'y': 1.224646799147353e-15, 'x': 0.0}
Ну ось і все
Нічого складного і неочевидного, як бачите, немає. Всього, чого, по-моєму, можна добитись так це більшої виразності і доступності коду. На більш складних прикладах з різними робочими процесами моделей це, мабуть, буде виглядати більш красиво.
Коментарі
Дописати коментар