Блоґ одного кібера

Історія хвороби контуженого інформаційним вибухом

Чим відрізняється декоратор від адаптера? (І про фасад)

with 2 comments

Цих вихідних їхав поїздом додому, і в купе побачив на столі книжку “Heads first Design Patterns” видавництва O’reilly. Моїми сусідами по купе були хлопець з дівчиною. Вони виглядали сонними, і навіть плутали напрям поїздки (думали що поїзд до Києва їде, а не до Франківська, хоча їхали до Франківська), і активно проявляли намір лягти спати, тому я запитав:

– Можна я вашу книжку подивлюсь? Бо завжди хотів знати чим відрізняється декоратор від адаптера.

На що хлопець відповів:

– Так, звісно.

А дівчина:

– Ви що, теж програміст?! Як вас всіх скрізь багато розвелось!

Вони лягли на свої полиці, я теж підклав під голову рюкзак, вмостився на своїй полиці і взявся за книжку.

Книжка чудова, всім рекомендую. Правда, напевне дорога як холєра, але якщо вам пощастить до неї дістатись – читайте без жодних сумнівів. Щоб довго не тягнути інтригу, скажу що про відмінність декоратора і адаптера я таки дізнався. Там в цій книжці навіть цілий діалог між декоратором та адаптером описують, де ці два шаблони доводять один одному хто з них крутіший. 🙂

Отож, і декоратор і адаптер – шаблони обгортки. Вони при створенні екземпляра себе приймають якийсь об’єкт, і запам’ятовують його десь в своїх атрибутах. Ключова відмінність – декоратор має той самий тип що й огортуваний ним об’єкт, тобто інтерфейс об’єкта що декорується не змінюється, просто його функціональність розширюється, а адаптер – змінює інтерфейс зовсім.

Як приклад для декораторів наводили клас – напій.

# coding=utf-8
class Beverage(object):
    def __init__(self,
        description = 'Невідомий напій', 
        price = 0,
    ):
        self._description = description
        self._price = price

    @property
    def description(self):
        return self._description

    @property
    def price(self):
        return self._price

class Tea(Beverage):
    def __init__(self,
        description = 'Чай',
        price = 2,
    ):
        super(Tea, self).__init__(description, price)

class Coffee(Beverage):
    def __init__(self,
        description = 'Кава',
        price = 3,
    ):
        super(Coffee, self).__init__(description, price)

tea = Tea()
print tea.description, tea.price # чай 2
coffee = Coffee()
print coffee.description, coffee.price # кава 3

А тепер уявімо собі що нам треба ще написати класи для тих самих напоїв, але з цукром, а ще з молоком. Можна було б наслідуватись, але тоді в нас буде ціла купа різних класів. Цукор і молоко – це декоратори напою.

class BeverageDecorator(Beverage): # важливо що декорований напій, це теж напій
    _description = 'Невідомий додаток'
    _price = 0
    def __init__(self, beverage):
        self.beverage = beverage
    
    @property
    def price(self):
        return self._price + self.beverage.price

    @property
    def description(self):
        return self.beverage.description + ' і ' + self._description

class Sugar(BeverageDecorator):
    _description = 'Цукор'
    _price = 0.1

class Milk(BeverageDecorator):
    _description = 'Молоко'
    _price = 0.2

my_coffee = Milk(Sugar(Sugar(Coffee())))
print my_coffee.description, my_coffee.price
# Кава і Цукор і Цукор і Молоко 3.4

Бачимо, що декорований напій – все одно напій, хоча й з дещо зміненою функціональністю. Адаптери натомість можуть взяти один об’єкт і повернути цілком інший.

class FahrenheitThermometer(object):
    @staticmethod
    def get_temperature():
        return 451

class CelsiusAdapter(object):
    def __init__(self, fahrenheit_thermometer):
        self.thermometer = fahrenheit_thermometer

    def __call__(self):
        return (self.thermometer.get_temperature() - 32) * 5.0 / 9

f = FahrenheitThermometer()
print f.get_temperature()
c = CelsiusAdapter(f)
print c()

Бачимо що адаптером ми можемо цілком поміняти інтерфейс нашого об’єкта, змусити його повертати інші дані, які запитуються іншим способом.

Багато із звичних декораторів в Python є насправді адаптерами. Тому можна сказати що в Python декоратор – це обгортка, яку крім того можна ще й особливим синтаксисом застосувати до огортуваного об’єкта. Той же @property, @staticmethod – змінюють інтерфейс виклику методу, роблячи його зручнішим. @twisted.internet.defer.inlineCallbacks – теж змінює інтерфейс, перетворюючи співпрограму (coroutine) на набір асинхронних функцій.

Крім того, є особливий вид адаптера, який адаптує зразу кілька об’єктів, тільки називають його фасадом. Наприклад, нехай ми маємо такі об’єкти як вольтметр, та амперметр, і хочемо виміряти потужність нашої лампочки. Для цього треба послідовно під’єднати амперметр, паралельно вольтметр, і помножити їх показники. Це можна абстрагувати ватметром (в якого в реальності все одно чотири клеми, але не зациклюйтесь на реальності, ми тут моделі будуємо):

class Voltmeter(object):
    def value(self):
        return 220

class Ammeter(object):
    def value(self):
        return 0.36

class Wattmeter(object):
    def __init__(self, voltmeter, ammeter):
        self.voltmeter = voltmeter
        self.ammeter = ammeter

    def value(self):
        return self.voltmeter.value() * self.ammeter.value()

w = Wattmeter(Voltmeter(), Ammeter())
print w.value()

Взагалі, це дуже часто застосовні адаптери, і їх не обов’язково знати щоб застосовувати. Але просто іноді варто знати значення цих слів, щоб розуміти про що люди пишуть.

Advertisements

Written by bunyk

Липень 9, 2014 at 08:40

Відповідей: 2

Subscribe to comments with RSS.

  1. Холера насправді не дорога)) Треба казати – “книга до холєри дорога”. Так моя бабця на Заході каже;-)

    п.с. гарно пишеш, підчитую

    Roman V. Slobodyan

    Вересень 5, 2014 at 13:55


Залишити відповідь

Заповніть поля нижче або авторизуйтесь клікнувши по іконці

Лого WordPress.com

Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

Twitter picture

Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

Facebook photo

Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

Google+ photo

Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

З’єднання з %s

%d блогерам подобається це: