Този пост е съставен от две неща – кратък rant за някои особености на българския език и странен паралел с designs patterns (които упорито няма да наричам „шаблони за дизайн“). Първо ще се опитам да ви убедя в загубената кауза да използваме „чуждици“ като „патърн“ (или „патерн“) и след това (за пореден път) ще се опитам да илюстирирам, че „дизайн патърните“ въобще не са нещо лошо, дори в език като Ruby.
Първо, няколко думи за употребата на design patterns. Ако не четете книгата отгоре-отгоре, ще видите, че това са „примери за добър дизайн, чиито идеи могат да се преизползват“, а не „рецепти за решаване на сходни проблеми“. По-важното нещо за преизползване са идеите зад pattern-ите, а не структурата на кода им. Дори имената служат за да навяват асоциации, а не да именоват структурно еднакви парчета код.
Това е изключително важно уточнение, поради най-честата грешка при прилагането на тази книга – преизползват се структурите (интерфейс IObserver
и клас AbstractObserver
), а не идеите зад тях (индирекция между клас, който прави нещо, и друг, който иска да знае за това).
Из превода
Следват, дефиниции на думите „шаблон“ и „pattern“. Едно речниково значение на „шаблон“ e:
мн. шаблони, (два) шаблона, м.
1 Образец (форма, калъп и др.), по който се изработват множество еднакви изделия. Шаблон за буква.
2 Прен. Само ед. Общоизвестен, изтъркан модел за творческа дейност, който води до еднообразие; липса на оригиналност. Мисли по шаблон.
3 Прен. Шаблонен израз. Речевите шаблони.
Ето и релевантната част от значението на „pattern“ от New Oxford American Dictionary:
pattern |ˈpat(ə)n|
noun
2 a regular and intelligible form or sequence discernible in the way in which something happens or is done: a complicating factor is the change in working patterns.
4 an excellent example for others to follow: he set the pattern for subsequent study.
Забавлява ме как този превод има грешка още в заглавието.
Впрочем, ето някои други възможности за превод, взети от българско-английския речник:
образец, пример, мостра, шаблон, еталон, кройка, диаграма, схема, стил, характер, устройство, строеж, структура, насока, тенденция, особеност
Аз лично харесвам първата – образец. Носи ми най-подходящата асоциация. И въпреки това, не ми харесва, понеже в този контекст „pattern“ е по-скоро жаргонна дума със специфичен смисъл.
Design patterns in Ruby, vol. 3
В кода на сайтовете на курсовете, които водим, имаме клас TaskChecker, който проверява предадените решения. Той прави следните неща:
- Намира всички решения, които трябва да пусне.
- Пуска тестовете на задачата с всяко решение (прави shell out за целта).
- Записва резултатите в базата.
Един от начините да пуснем проверка е с бутона „Провери решенията“ в сайта. Използва се message queue (Sidekiq) за да се изпълни всичко в background-а. Когато той приключи, резултатите са в базата и могат да бъдат разгледани от администратор, преди да бъдат обявени публично. Кода в worker-а е следния:
checker.run
Банален клас с банален интерфейс.
Втората употреба е от Rake задача. Преподавателите могат да изтеглят решенията при себе си и да ги пуснат в локална версия на системата. Това е удобен начин да се провери дали тестовете са адекватни – понякога се „сгъбват“ с работещи решения на студентите, а друг път откриваме, че липсват проверки за често срещани грешки.
Ще бъде хубаво, ако Rake задачата показва някакво състояние: например, след всяка проверка показва колко теста са минали и колко не са. Кодът е следния:
checker.on_each_solution do |s|
puts "##{s.user_id} Passed: #{s.passed_tests} Failed: #{s.failed_tests}"
end
checker.run
(В оригинала променливата се казва solution
, но тук я прекръстих на s
за да бъде по-кратко)
Разликата е в извикването на on_each_solution
. За незнаещите Ruby, това е като извикване на метод с един аргумент, който е анонимна функция. Последната има един параметър и принтира низ. TaskChecker
запазва блока и го извиква след всяка проверка.
Как наричам това? Observer.
Но защо? В крайна сметка, няма еквивалентите на IObservable
и IObserver
. Няма addObserver
, removeObserver
и прочее.
Есенцията на този „образец за дизайн“ е индирекцията между класа, който изпълнява задачите, и кода, който го ползва. Тук имаме два клиента – worker-а на Sidekiq и Rake задачата. Първият го ползва единствено заради страничните му ефекти, докато вторият има нужда да знае малко повече и да реагира по подходящ начин (извежда по един ред за всяка проверка). Това е стандартно приложение на observer.
Какви биха били алтернативите?
TaskRunner
може винаги да принтира резултати. Докато вероятно няма да пречат на никого (логват се някъде от sidekiq), това е мърляво решение.TaskRunner
може да приема булев параметър дали принтира резултати. Една идея по-добре, но също мърляво. Логиката за извеждане на резултати в текстова конзола (била тя и един ред) няма работа там. Също, ако ни трябва трети клиент (например dump до CSV файл), няма как да стане.TaskRunner
може да приема форматиращ низ. Това ще реши (някак) проблема с конзолата и CSV-то, но става несериозно.- Rake задачата повтаря кода и добавя принтиране. Кодът е достатъчно малък, за да може да се copy/paste-не и промени, но това „решение“ също има своите проблеми. Ако част от логика се промени (намиране на решения, изпълняване на проверка, добавяне на резултати в базата) ще трябва да се сетим да обновим и rake задачата. Това лесно се забравя.
Ето, че с малко вдъхновение от книгата може да измислим просто и елегантно решение. Ключовият момент е използването на observer като „pattern“, а не като „шаблон“.
Null Object
Впрочем, има още един „дизайн образец“ в TaskChecker
– видяхте ли го? Намира се в конструктора. Полето @on_each_solution
се инициализира с lambda { |_| }
, което е функция на един аргумент, която не прави абсолютно нищо. Така run
няма нужда от if
, с който да проверява дали @on_each_solution
е nil
.
Това напомня на друг популярен pattern – Null Object. Това е хубав начин да се справим със специални случаи, без да експлозия от if
-ове.
Не мисля, че null object-а е наложителен в този код – би бил достатъчно прост и с един if
в run
. Но не пречи.
В заключение
Design Patterns не е книга с рецепти, а каталог с идеи. Те са широко приложими, особено ако се вземе предвид как се пренасят в различни езици.
А „шаблони за дизайн“ е лош превод.
Браво, Стефане! „Образец“ е изключително удачен превод на pattern. В тазвечершната лекция обектно-ориентираното програмиране ще взаимствам щедро от теб (=
Скромно предложение за допълнение: В поста говориш за „книгата“, метни един параграф увод за недоразбралите, за коя книга говориш (предполагам ‘Design Patterns: Elements of Reusable Object-Oriented Software’) и за кой превод (има я на български?).
В повечето случаи в които стане дума за design patterns, разговорът бързо отива към крайни мнения и доволно количество догматизъм. Всъщност от толкова много време не бях чувал разумно изказване по тази тема, че трябва да призная че бях силно (и приятно) изненадан от поста ти. Евала!