Недавно на Хабре появилась любопытная заметка Как два программиста хлеб пекли . В ней автор противопоставляет подход «сделай все с кучей классов и паттернами проектирования» подходу «сделай как можно проще (KISS)». А вот интересно, что будет, если попытаться решить описанную в статье задачу с помощью функционального подхода?
Итак, сидит себе такой программист на Haskell . Тут к нему подходит менеджер и заявляет, дескать, нам нужно научиться печь хлеб. В общем, ничего толком не ясно, но раз надо, так надо:
createBread = Bread
Позже становится известно, что хлеб, оказывается, должен печься в печке:
data Bread = Bread
createBread :: Oven -> Bread
createBread _ = Bread
Как видите, пока это мало на что влияет. Как и то, что печи бывают разных видов:
data Bread = Bread
createBread :: Oven -> Bread
createBread _ = Bread
Первое действительно более-менее интересное условие состоит в том, что газовая печь не может печь без газа:
data Oven = ElectricOven | GasOven | MicrowaveOven
data Bread = Bread
breadCouldBeCreated GasOven GasUnavailable = False
breadCouldBeCreated _ _ = True
createBread oven gas
| breadCouldBeCreated oven gas = Just Bread
| otherwise = Nothing
Здесь вводится тип «состояние газа». Газ — он либо есть, либо его нет. Если мы используем газовые баллоны, можно хранить количество доступного газа в литрах, суть от этого не меняется. Функция breadCouldBeCreated проверяет, можем ли мы что-нибудь приготовить при текущих обстоятельствах (наличие газа и тип печи).
Позже становится известно, что помимо хлеба в печи также можно готовить торты и пирожки с различной начинкой:
data Food = Cake | Bread | Pasty Stuffing
data GasStatus = GasAvailable | GasUnavailable
data Oven = ElectricOven | GasOven | MicrowaveOven
ovenCouldBeUsed GasOven GasUnavailable = False
ovenCouldBeUsed _ _ = True
create food oven gas
| ovenCouldBeUsed oven gas = Just food
| otherwise = Nothing
Вводим типы «еда» и «начинка». Функцию breadCouldBeCreated переименовываем в ovenCouldBeUsed.
Теперь менеджер хочет, чтобы торты, пирожки и хлеб пеклись не просто так, а по разным рецептам. Сказано — сделано:
data Food = Cake | Bread | Pasty Stuffing
data GasStatus = GasAvailable | GasUnavailable
data Oven = ElectricOven | GasOven | MicrowaveOven
ovenCouldBeUsed GasOven GasUnavailable = False
ovenCouldBeUsed _ _ = True
create food oven gas
| ovenCouldBeUsed oven gas = Just food
| otherwise = Nothing
breadRecipe = create Bread
cakeRecipe = create Cake
pastyRecipe stuffing = create $ Pasty stuffing
Рецепт обычно предписывает выполнение каких-то действий с ингредиентами, печью и так далее. Очевидно, что рецепт представляет собой функцию высшего порядка.
Наконец, в печах требуется обжигать кирпичи. Судя по формулировке, обжигать кирпичи следует в тех же самых печах, включая микроволновую (хотя в статье на Хабре вводится отдельный класс Furnace):
data Food = Cake | Bread | Pasty Stuffing
data GasStatus = GasAvailable | GasUnavailable
data Oven = ElectricOven | GasOven | MicrowaveOven
data Brick = Brick
ovenCouldBeUsed GasOven GasUnavailable = False
ovenCouldBeUsed _ _ = True
create food oven gas
| ovenCouldBeUsed oven gas = Just food
| otherwise = Nothing
breadRecipe = create Bread
cakeRecipe = create Cake
pastyRecipe stuffing = create $ Pasty stuffing
makeBrick oven gas
| ovenCouldBeUsed oven gas = Just Brick
| otherwise = Nothing
Здесь мы просто добавили тип «кирпич» и функцию «сделать кирпич».
Выводы напрашиваются сами собой. Мы получаем простой для понимания и легкий в сопровождении код. В процессе его написания мы легко и непринужденно строим модель предметной области, фактически просто делая перевод с русского на Haskell. Безо всяких там наследований, рефакторингов и UML.
Возможно, в действительности имелось в виду, что каждая печь производит хлеба и торты несколько иначе, и нам понадобится ввести дополнительный класс типов (или, если хотите, «интерфейс») с соответствующими экземплярами классов. Заодно нам не придется вручную кодировать вызов ovenCouldBeUsed всюду, где используется печь. Но не похоже, что решение существенно усложнится от всего этого.
А как вы печете хлеб?