SlideShare una empresa de Scribd logo
1 de 33
Descargar para leer sin conexión
Software Design Patterns:
Минало, Настояще и Бъдеще
Въведение
Понятието дизайн патерн (design pattern), що се касае до софтуерното инженерство, се дефинира
като общо решение за често срещан проблем, което може да бъде използвано многократно (Wiki, 1). Не
е учудващо че тази дефиниция най-вероятно може да бъде приложена към всяка една друга сфера на
организирана дейност. Това е защото идеята е стара, колкото и популярният израз за това, че е не нужно
човек да открива топлата вода (или колелото, или Америка) – това вече е направено отдавна. Този израз
най-добре носи посланието на настоящия документ, както и на самата идея за дизайн патерните.
Преди да премина към кратка история на зараждането и формалната употреба на термина, бих
желал да направя кратко речниково уточнение касаещо понятието дизайн патерн(и) и многократната му
употреба в този документ именно в оригинал, макар и на кирилица, подобно на добре установила се
чуждица в езика ни. В целия документ този термин се среща в оригинал, поради убедеността ми в
невъзможността за точен превод на български език. Преводът е възможен, но всяка вариация го
отдалечава в една или друга степен от оригиналното значение.
Първата дума - дизайн – отдавна присъства като чуждица в българския език и се е наложила като
една от най-често употребяваните думи за описване на творческата дейност по създаването на някакъв
нов продукт. Тъй като така и така тази чуждица си е извоювала мястото в езика ни, не смятам че
употребата й представлява проблем. Втората дума обаче е главният виновник за това терминът да се
използва предимно в оригинал. Думата „pattern“ има много възможни преводи в българския език, според
контекста на употребата, но нито един не е 100% точен. Сред най-популярните са модел, шаблон, скица и
др. Нито една от тези думи не носи обаче общия смисъл на думата в оригинал извън контекста.
Търсенето на патерн примерно в поведението на даден човек, както профайлърите от ФБР правят, най-
точно се определя като търсене на закономерност, на повтарящи се елементи, на модел на поведение, но
нито една от тези думи пък не е подходяща при употребата на термина в софтуерното инженерство, още
по-малко пък в общия смисъл на думата. Затова, за да не развалям цялото изложение с комични
термини от рода на небезизвестната драсни-пални-клечица, реших да запазя оригинала и да го
употребявам непроменен.
В съвременната наука терминът се налага и добива популярност благодарение на убежденията,
книгите и работата на архитектът Кристофър Александър през 70-те години на XX в. Той е бил убеден че
решаването на класически човешки нужди чрез съответните архитектурни решения е допринесло за
сътворяването на всички велики архитектурни шедьоври от миналото. За него успешните сгради са тези,
чрез които архитекта посреща нуждите на хората, които ги ползват, и това ги прави красиви и
хармонични. Акцентира се на ролята на потребителя на сградата, като определящ нейната функция и от
там съответно на архитектурата, която има за цел да отговори на тази нужда. Кристофър Александър е
вярвал в почти мистичният смисъл на думата в това че съществува вечен, неподвластен на времето стил
или начин за строене, който се крие в спазването на гореспоменатите правила. (Wiki, 2)
За да стигне по-близко до дефинирането на този процес, Кристофър Александър написва няколко
книги, една от които е “A Pattern Language: Towns, Buildings, Construction“, в която се опитва да дефинира
именно тези повтарящи се решения за многократната употреба на общи нужди в архитектурата, които
определят полезността и от там красотата на сградата. Идеята в тези негови трудове, както и
систематичността на изложените примери вдъхновяват през 1987г. Кент Бек и Уард Кънингам да пробват
да намерят дизайн патерни в програмирането. Публикуваните от тях резултати запалват и други колеги от
софтуерния бранш да опитат същото.
Най-известната книга по тематиката е “Design Patterns: Elements of Reusable Object-Oriented
Software“, чиито автори са Ерих Гама, Ричард Хелм, Ралф Джонсън и Джон Влисидес, по известни под
прозвището „бандата на четиримата“. Книгата е издадена през 1994г. и описва 23 класически софтуерни
дизайн патерни с примери на C++ и Smalltalk. Те са изложени в своеобразен каталог, според функцията
която изпълняват, на Creational (създаващи), Structural (структурни) и Behavioral (поведенчески) патерни.
Всеки патерн е документиран с точно определен брой характеристики, които помагат на програмиста да
определи дали именно този патерн е най-подходящият за решение на настоящия проблем. (Gamma,
ch.III)
Освен споменатата каталожна част с детайлната категоризация и примери за всеки патерн, книгата
на „четиримата“ може да се похвали и с две много добри въвеждащи глави. Първата има за цел да
аргументира употребата на дизайн патерни, като разясни предимствата на този вид работа и даде насоки
за ползотворното им приложение (Gamma, ch.I). Някои от тези насоки са отразени и в настоящия
документ. Втората част включва нагледни примери за изграждането на различни приложения
посредством дизайн патерни, като това позволява на читателя да види именно как даден дизайн патерн
влиза в употреба и така да се каже си намира мястото в дадено софтуерно решение (Gamma, ch.II).
От издаването на тази книга до днес са минали почти 15 години. Когато книгата е писана не само че
не е имало уеб 2.0, gmail.com, facebook, но и добрия стар уеб 1.0 едва е прохождал, а PHP (тогава още
без версия) е представлявало скромна колекция от малки CGI бинарни програмки, написани на С,
изпълняващи ролята на лични Personal Home Page Tools на Размус Лердорф (Wiki, 3). Това обаче по
никакъв начин не променя стойността на отразените в нея решения. Това се дължи на няколко причини:
Както Кристофър Александър е забелязал що са касае до архитектурата, добрите решения са полезните
решения и полезните решения са вечни. Основните архитектурни проблеми на софтуерната архитектура,
пред които са поставени програмистите, принципно са едни са едни и същи, независимо дали говорим за
локално приложение на C или за разпределена уеб-базирана система. Инструментите, платформите и
програмните езици се развиват, но проблемите остават изконно същите, за разлика от инструментариума.
Така или иначе, основната бизнес логика на уеб приложенията, в случаите когато са написани
кадърно и с поглед в бъдещето, се съдържа в набор от моделни класове, които нямат нищо общо с уеб
характера на приложението. Тези структури от данни, обслужващи бизнес модела, независимо от езика
на който са писани, не се различават по функция от тези които са обслужвали същите бизнес нужди, но
по друг, не уеб-базиран начин преди 15 години. Още повече, че в днешни дни AJAX технологията
приближава уеб приложенията все повече до локалните софтуери, като приложението на дизайн
патерните се простира отвъд бизнес модел класовете и достига богатия потребителски интерфейс от
страна на клиента, както и комуникацията между него и сървъра. Това, както ще видите по-напред в
изложението, е видно и в зараждането на днешните AJAX патерни, които в своята основа често повтарят
или имитират класически дизайн патерни, споменати в книгата на „четиримата“.
Характеристики и Класификация на Дизайн Патерни
Въпреки че няма универсален стандартен начин по който да се документират дизайн патерни,
форматът на свойствата им, наложен от „четиримата“, е най-близкото до стандарт за описване на дизайн
патерн или поне е достатъчно меродавно, че авторите след тях да се водят по него. Тези основни
свойства, на които авторите дават стойност за всеки патерн са:
− Име и класификация: Уникално име, служещо за идентификация на патерна
− Замисъл (intent): Описание на целта на въпросния патерн и обуславяне на причината да се ползва
− Синоними: Други наименования, под които е познат същия патерн
− Мотивация: Примерен сценарий, представящ проблем и контекст, в който патерна може да бъде от
полза
− Приложимост: Ситуации, в които е приложим патерна – помагат да изясняване на контекста
− Структура: Графично представяне на патерна, обикновено посредством употребата на UML
средства, като Class и Interaction диаграми.
− Участници: Списък с всички класове и обекти, използвани в патерна с техните роли в софтуерния
дизайн
− Сътрудничество (collaboration): Описание на това как класовете и обектите използвани в патерна
си взаимодействат
− Последствия: Описание на резултатите, страничните ефекти и недостатъците свързани с употребата
на този дизайн патерн
− Имплементация: Описание на имплементацията на патерна, на реалното решение
− Примерен код: Примерна употреба на патерна, реализирана на конкретен програмен език
− Познати употреби: Примери на реална употреба на патерна в съществуващи решения
− Свързани патерни: Други патерни, които имат връзка с настоящия. Обикновено се правят сравнения
между него и изброените.
(Gamma, Ch. 1)
Тази детайлна схема на описание на даден патерн в по-голяма или по-малка степен е взаимствана
от други автори от излизането на гореупоменатата книга до наши дни. Пример за това е сайтът
ajaxpatterns.org (ще бъде разгледан по-подробно по-натам в изложението), посветен на събирането и
категоризиране на добри практики и дизайн патерни при реализирането на AJAX-базирани уеб
приложения. Въпреки че някои от наименованията на свойствата се различават и броят им не е 100%
същия, принципно те са доста близко до оригиналните. Това също илюстрира големия ефект, който
книгата на „четиримата“ оказва и до днес в професионалните среди.
Както вече бе споменато, патерните изброени и описани в каталожната част от книгата са разделени
на Creational (създаващи), Structural (структурни) и Behavioral (поведенчески) патерни. Тези категории
са условни, според основната функция на патерните, но много по-сетнешни автори въвеждат
допълнителни такива, обикновено специфични за даден по-специален вид приложения. Примери за
такива категории са:
− Concurrency: съгласуващи или успоредни – касаещи много-процесните и много-нишкови приложения
− Distributed: разпределени – касаещи софтуерни решения, разпределени да работят на повече от
един компютър
− Usability: касаещи потребителското взаимодействие със системата
Също така, има една по-горна категория софтуерни дизайн патерни, която обхваща не решаването
на конкретен проблем, а цялостната архитектура на дадено софтуерно приложение. Това е групата на
архитектурните патерни. Пример за такива са небезизвестният MVC (Model-View-Controller – ще бъде
разгледан по-късно), Three-Tier, Pipeline, Peer-to-Peer, Service-Oriented Architecture, Layers и др (Wiki, 4).
Съвременните уеб-базирани приложения, освен базисните създаващи, структурни и поведенчески
части, често имат елементи и от разпределените системи, проблеми свързани с потребителското
взаимодействие, както и дори понякога успоредно протичащи процеси (особено често при скриптовите
езици от страна на клиента). Добре избраната архитектура на цялото приложение неминуемо играе
огромна роля за ефективността и гъвкавостта му. Следователно, полезните дизайн патерни за едно уеб
приложение трябва да се търсят в почти всички известни категории такива. Започвайки от класическите
три категории (Creational, Structural, Behavioral) и разпростирайки се върху още няколко, ще се опитам да
направя едно полезно изложение на избрани дизайн патерни, които са незаменимо полезни при
разработката на едно добро функционално и гъвкаво съвременно уеб приложение.
Създаващи (creational) дизайн патерни:
Групата на създаващите патерни включва такива, които се отнасят до създаването на нови обекти –
механизмите които се използват за да се създават те по най-подходящия за ситуацията начин. Те се
използват, когато получаването на обект от даден клас по класическия начин – чрез инстанциране на нов
обект – не е най-подходящия начин, поради някакви причини. Пример за това е създаването на обект от
клас, който представлява абстракция за връзка към база данни. Ако при всяко създаване на нов такъв
обект се отваря нова връзка към базата, то прекомерната употреба по този начин би довела до бързо
изразходване на позволения от сървъра брой връзки към базата. В това отношение няма сериозна
разлика между приложенията от преди 15 години и тези сега – всички те ползват от време на време
някакви външни ресурси, било то файлове, потоци, или бази данни, като достъпът до тези ресурси е
ограничен и трябва да се пести и да се пази от претоварване.
Създаващите патерни предлагат гама от решения на този вид проблеми посредством патерните
Factory Method, Factory, Abstract Factory, Object Pool и Singleton. Има и други създаващи дизайн
патерни, като например Builder, Prototype и Lazy Initialization, но целта ми не е да разгледам всеобхватно
всички, а да фокусирам върху тези от тях които са особено полезни и ключови при създаването на уеб
приложения.
В основата на решението на проблема с нежелания прекомерен достъп стои идеята даден
споделен общ за целия софтуер ресурс да бъде достъпван чрез един единствен обект, за да се избегне
нежелана дупликация или претоварване. Това се осъществява обикновено посредством Singleton.
Singleton се използва за ограничаване употребата на даден клас до един единствен обект. Обяснено с по-
прости думи, Singleton може да бъде разглеждан като едно по-културно и мащабируемо решение –
еквивалент на иначе простата глобална променлива, която бихте могли да инициализирате в някаква
обща за целия софтуер начална точка и да ползвате като глобална в последствие където е необходима.
Освен че се придържа към ООП стила на програмиране и капсулира данните, не „замърсявайки“ общото
пространство с допълнителни данни, Singleton превъзхожда глобалната променлива по това че не е
нужно да помните, че трябва да го инициализирате в някаква начална точка, предхождаща първата му
употреба. Както ще стане очевидно от примера по-долу, той просто се инициализира сам при първата си
употреба и тази същата инстанция се ползва при всички последващи употреби. Това също е плюс в
случаите когато изобщо не се стигне до употреба – тогава просто не се случва никакво инстанциране на
въпросния обект.
Следва пример за употребата на глобална променлива като носител на обект за връзка с база
данни и пример за употребата на Singleton за същата цел:
$db_conn = new DBConnection( $db_name );
.
.
//Later in some other file
class X {
...
function methodX1() {
global $db_conn;
$res = $db_conn­>query( 'SELECT * FROM t1' );
...
}
function method X2() {
global $db_conn;
$res = $db_conn­>query( 'SELECT a, b FROM t2' );
...
}
...
}
А ето как изглежда същият пример с употребата на Singleton:
require 'DBSingleton.class.php'
...
// ­        По натам в някой друг файл
class X {
...
function methodX1() {
$db_conn = DBSingleton::getInstance();
$res = $db_conn­>query( 'SELECT * FROM t1' );
...
}
function method X2() {
$db_conn = DBSingleton::getInstance();
$res = $db_conn­>query( 'SELECT a, b FROM t2' );
...
}
...
}
Ето и самият Singleton клас, унаследяващ оригиналния клас за базата данни, като това е само
един вариант, не е задължително да ставя чрез унаследяване:
class DBSingleton extends DBConnection{
private static $instance;
protected final function __construct( $db_name ) {
parent::__construct( $db_name );
}
public static function getInstance() {
     if( !self::$instance instanceof self )
self::$instance = new self;
return self::$instance;
}
}
Поредното предимство, което авторите на ”Design Patterns...” книгата изтъкват е, че ползването на
глобалната променлива, макар да е гаранция за достъпност на въпросния един обект, не е гаранция за
това че не може да бъдат създадени още от този тип. Ограничаването на създаването на нови обекти от
дадения тип чрез Singleton е доста по-надежден начин да се предпази кода от нежелани допълнителни
инстанцирания. Това може да се гарантира допълнително, ако оригиналният DBConnection клас се
дефинира като abstract, за да не може да се извика директно конструкторът му. (Gamma, p.127)
Singleton е един от най-простите от гледна точка на брой използвани класове и обекти патерн – той
съдържа само един клас и един обект в него. Това го прави удобен като градивен елемент на по-сложни
създаващи патерни. Типичен ползвател на Singleton е Abstract Factory патернът. Идеята на Abstract
Factory е да предложи на кода-клиент достъп до набор от конкретни „фабрики“, които произвеждат даден
тип обекти. В тази концепция отново е заложена идеята, както в Singleton, че понякога създаването на
нови обекти от даден тип по класическия начин с new не е най-доброто решение. Докато при Singleton
това обикновено се дължи на нежеланието да се прекалява със създаването на нови обекти от дадения
тип, то при фабриките причината по-скоро се крие в „незнанието“ за това какъв конкретно обект от даден
тип му трябва на кода ползвател, за да си свърши работата. По този начин софтуерният архитект може да
създаде гъвкави решения за дадени категории проблеми, без да трябва да задълбава в конкретни
типове. Тук се разчита на общия интерфейс на всички обекти, споделящи някакъв по-горен общ
родителски клас.
(Gamma, p.87)
Горната схема е взета от примерите за употреба на Abstract factory книгата на „четиримата“, като
конкретният пример касае създаване на архитектура от класове, обслужваща графично приложение, като
своеобразната библиотека за графични компоненти може да обслужва повече от една платформа за
визуализация. Прилагайки примера към нуждите на съвременните уеб приложения, може да го сравним
със сходна нужда – създаването на JavaScript библиотека за визуални компоненти от рода на кутии със
съобщения или грешки, диалогови прозорци или кутии за потвърждение или отхвърляне на дадено
действие и др. подобни. Целта на библиотеката обаче, освен функционалността на тези елементи, е да
предостави на потребителите и добър външен вид на елементите, като този външен вид може да се
настрои да приеме една от няколко визуални теми, например в стил Windows или Mac OS X. Кодът който
ползва елементите не трябва да се интересува от настройката за стил на визуализация, а да се обръща
към всички обекти произведени от дадена абстрактна фабрика ползвайки еднакъв споделен интерфейс,
независимо от визуалната им специфика:
Въпреки че употребата на Singleton не е задължително условие за имплементацията на Abstract
Factory, то определено е едно от популярните и ефективни такива. Конкретните фабрики, които
абстрактната фабрика ползва, са подходящи кандидати за Singletons, поради това че не е нужно да се
създава повече от една от даден тип, а и възможността някои от тях да не бъдат създадени изобщо, ако
не възникне нужда от обект, който те да произвеждат.
Друг много популярен похват/патерн в софтуерното инженерство, често реализиран посредством
Singleton, е Object Pool. От една гледна точка, Object Pool може да бъде разглеждан като разширена
версия на Singleton, в която достъпът до въпросният контролиран обект от даден тип е ограничена не до
един-единствен обект, а до набор от определен брой такива, които всеки може да взима за ползване и
после да връща, подобно на книги от кварталната библиотека. Говорейки с конкретни примери, пак може
да става въпрос за достъп до база данни или сокети, файлове, отворени и логнати FTP връзки или
какъвто и да било друг ресурс (обикновено външен), който е „скъп“ от гледна точка на памет или време за
създаване и за това е желателна многократната му употреба преди да бъде разрушен, за да се спести
времето за повторното му създаване.
Ето една примерна реализация на Object Pool на PHP5, която реализира достъп до отворени FTP
връзки към даден сървър. За примера се приема, че правим връзки към един и същ отдалечен FTP
сървър и че няма да се интересуваме от предаване на потребителски имена или пароли (FTPConn класа
да му мисли – приемаме че си има свои начини да прочете тази конфигурационна информация от
някъде). Всяка отворена връзка също така има валидност във времето, съобразена с предполагаемия
(или известен) размер на времето след което отсрещният сървър ще затвори връзката заради
неактивност. Object pool-ът следи тази валидност, за да не се случи да предостави на „клиентите“ си
изтекла връзка. Приема се, също така, че имаме клас на име FTPConn, обектите от който осъществяват
свръзката и FTP операциите.
static class FTPPool{
private static $ftp_conns = array();
public static function getConn() {
     if( count( self::$ftp_conns ) )
{
do{
$ftp = array_shift( self::$ftp_conns );
} //May add more conditions for expiring connections, like time
while( $ftp != null && !$ftp­>isConnected() ); 
if( $ftp )
return $ftp;
}
$ftp = new FTPConn();
$ftp­>connect();
return $ftp;
}
public static function returnConn( $conn )
{
//Reset any state­related info here, if needed, reset clocks, if any
return  array_push( self::$ftp_conns, $conn );
}
}
Както е видимо, самият Object Pool е Singleton – статичен е и достъпът до него се осъществява
посредством статични методи, които манипулират вътрешен статичен масив от активни, изградени
връзки. Първият пък когато някой код-клиент на Object Pool-a извика FTPPool::getConn()    той ще
създаде нов обект (и ще го закачи), защото вътрешният му масив ще е празен. Следващият път, когато
този метод бъде извикан, ако първата създадена връзка-обект още не е върната посредством
FTPPool::retunConn( $conn ),  ще продължат да се създават нови връзки, защото другите още са
в успоредна употреба. В момента който употребените обекти-връзки почнат да се връщат, вътрешният
масив ще почне да се пълни, след като рециклира всяка една от тях, ако това е нужно. Тогава вече,
getConn() ще почне да връща от рециклираните, при положение че те все още са закачени успешно за
отдалечения FTP сървър.
Тук е моментът да се упомене, че Object Pool-a може да налага още повече контрол върху броя и
качеството на връщаните от и към него обекти. Може да се наложи максимален размер на pool-a, който
да се следи да не бъде нарушаван (да почне да хвърля exceptions, например при поискване на поредния
обект, който вече е с един повече от допустимото), може активно да рециклира статуса на върнатите
обекти, спрямо някакъв еталон, може също така и да наложи трайност на обектите в pool-a, така че да не
връща обекти които са изтекъл “срок на годност”. Възможностите са много, нуждата се определя от
характера на обектите, които биват обслужвани. Също така е честа разновидност Object Pool-а е
моделът, чийто pool представлява всъщност двумерен масив от pools, разделени по типове според ключа.
Например, ако нашият object pool от примера трябва да обслужва N на брой FTP сървъри, може хост
името на дадения FTP сървър да се ползва като ключ към масивът от обекти-връзки към въпросния
сървър.
Връзките към отдалечени сървъри (и като цяло I/O операциите) се смятат като цяло за едни от
частите на приложението, които най-много допринасят за забавяне на изпълнението, но далеч не са
единствените обекти, които си струва да бъдат извличани от Object Pool. В едно приложение с много
потребители и като цяло много изпълнения на даден скрипт за единица време, дори един по-тежък като
структура обект с доста свойства може да си струва да бъде извличан посредством Object pool, при
положение че характера му и архитектурата на приложението го позволява.
Последният от създаващите патерни, който искам да разгледам е Prototype, макар и не толкова
детайлно колкото предишните. Prototype също работи в посока да не се създават новите обекти от даден
тип посредством new оператора, но той стига още по-далече от Singleton и Object Pool, като цели дадени
класове да се ползват за създаването на само един обект (x) от всеки клас (ClassX) и после всяка
следваща инстанция от този клас да се създава не чрез извикване на new ClassX, а чрез клониране на
вече създаденият прототипен обект x. За разлика от Singleton и Object Pool, Prototype няма за цел да
ограничава броя на създадените обекти от даден клас, той просто спестява използването на new за тази
цел, като го заменя с клониране на съществуващ прототипен обект. Подобен подход е подходящ за
създаване на обекти, които могат да имат само няколко дискретни състояния. Тогава може просто да се
създават чрез клониране от обект-прототип в желаното състояние.
Интересно е да се отбележи, че има обектно-ориентирани езици за програмиране, в които
създаването на нови обекти изцяло се осъществява чрез prototyping. Такива са Self, Omega, както и
добрия стар познат на всички JavaScript / ECMAScript. В тези езици унаследяването става чрез създаване
на нов обект, клониран от даден родител-прототип, като на новосъздадения клонинг динамично се
добавят нови свойства и методи. В езиците в които пък няма loose-typing, Prototype е удобен патерн за
имплементация на полиморфизъм. Тогава от базовия клас се създават различни обекти-прототипи, които,
посредством специален клас-мениджър на прототипите, пък служат за клониране на нови обекти, които
пък от своя страна вече имат различна имплементация на базовата функционалност. Това допринася за
динамиката на приложението в run-time, която по принцип е по-слабо изразена при strong-typing езиците.
Структурни дизайн патерни:
Структурните патерни се занимават с комбинирането на класове или обекти с цел съставяне на по-
мащабни структури. Тези патерни условно се делят на такива които касаят класове и такива които касаят
обекти. Структурните патерни за класове помагат за изграждането на интерфейси или имплементации,
съставени от повече от един под-класове. Тези пък, които касаят обектите, касаят комбинирането на
обекти с цел добавянето на нова функционалност по време на изпълнението на програмата.
Все повече уеб сайтове и приложения се изграждат посредством употреба на система за
управление на съдържанието (CMS – Content Management System). Неведнъж ми се е налагало да
имплементирам различни по мащаб подобни системи с цел реализирането на гъвкаво и лесно за
управление и поддръжка приложение. Всеки който е работил или правил подобна система може да ви
каже, че един от ключовете към успешното реализиране на такова решение е разглеждането на уеб
страницата като съвкупност от визуални модули. Важно е доброто модулиране на компонентите, така че
те да могат да бъдат използвани гъвкаво, да бъдат размествани и вграждани един в друг свободно и да
са в някаква степен универсални. Composite дизайн патернът е един от ключовете към създаването на
подобен вид визуални модули, които, подобно на части от детски конструктор, да съставят по-сложни
съставни компоненти, запазвайки еднакви свойства и поведение.
(Gamma, p.163)
Composite често е естествен избор при съставянето на йерархия от обекти от някакъв рекурсивен
характер, каквито примерно са елементите в едно XML/xHTML дърво. Всеки елемент от дървото или е
краен, или е съставен от други крайни или съставни елементи. Рекурсията е залегнала в природата на
този патерн и това го прави доста практичен за много приложения. Освен визуализацията на уеб
страници чрез модули и съставянето на рекурсивни дърва при парсването на mark-up езици, Composite е
и редовен избор за правене на рекурсивни менюта и други подобни атрибути на един уеб сайт. Тъй като
доста често информацията да тези елементи идва от записи в база данни, които изграждат въпросните
йерархии, Composite структурата се прилага успешно в изграждането на рекурсивен Active record (патерн,
който ще бъде споменат по-напред в изложението), така че когато на един обект-представител на
йерархията, обикновено корена на дървото, се извика методът за зареждане или рендиране, той свършва
своята лична част и после рекурсивно извиква същият метод върху своите съставни от същия вид, ако
притежава такива.
Пример за подобен вид рекурсивна употреба на Composite е ползването му за описване,
изграждане и визуализиране на едно рекурсивно меню с неизвестна дълбочина и степен на разклонение.
Много често в уеб приложенията имаме именно подобен сценарий за употреба на меню, което се
генерира динамично, на база записи в база от данни, и има рекурсивна структура без ограничения в броя
на нивата. Рендирането на подобно меню се подразбира че трябва да е продукт на рекурсивна функция,
както и обхождането му с цел намиране на даден елемент, както и почти всички действия върху него
(зареждане, изтриване и т.н.).
Ако си представим, че всеки елемент от менюто е наследник на даден MenuComposite клас, то
освен собственото си съдържание като ред в менюто (заглавие и хипервръзка), той може да съдържа
подменю с още N други елементи, всеки от тях сходен на описания. Съгласно горната схема на
Composite, ако елементът от менюто няма подменю с елементи , то той се явява крайно листо
(MenuLeaf). В противен случай той е MenuComposite.
Ето примерна структура на класовете, изграждащи структурата на менюто:
Целта е абстрактният клас MenuElement да е достатъчно общ, така че да може да представя както
простите елементи, така и съставните. Всички елементи в йерархията споделят метода render(), който
генерира чрез даден HTML шаблон техния краен HTML код. Рекурсивното изпълнение на този метод по
йерархията представлява едно обхождане на дървото по ширина, в следствие на което съвкупността от
генерирания HTML код от всеки елемент формира крайния HTML на менюто. Децата на MenuComposite
класа са наследници на MenuElement, за да може към тях да се обръщат със същия набор от методи,
като към другите елементи, независимо от характера им.
Още по-голямата гъвкавост и сила на Composite идва от факта, че не е нужно въпросните елементи
които се генерират да са от един и същи тип за да работи системата. Както е показано в горния пример,
сред листовите елементи може да има някакви декоративни разделителни линии или заглавия на менюто
или други подобни, които също са наследници на абстрактния MenuElement.
Друг популярен и удобен създаващ дизайн патерн е Proxy. Proxy е патерн, който има толкова много
приложения, че е трудно да бъдат изброени, но придържайки се предимно към уеб и уеб 2.0
приложенията, ще се опитам да дам нагледни примери за неговата универсалност и широко приложение.
Много програмисти са използвали този патерн при реализацията на приложения с богати потребителски
интерфейси, без дори да подозират за това. Едно от типичните приложения на прокси патерна е да
действа като заместител/представител на дадени обекти, поради невъзможност или нежелание по
някаква причина (сигурност, ефективност, скорост) да се работи с оригинала.
Почвайки от ситуацията с невъзможността за работа с оригинала, Proxy често се използва в
хетерогенни програмни среди, каквито са уеб 2.0 приложенията, където се използват повече от един
програмни езици, кодът на които се налага да комуникира успешно за да се реализира пълната
функционалност. Такъв е сценарият, в който даден богат потребителски интерфейс зарежда еднократно,
обикновено при първоначалното си зареждане в браузъра на клиента, някакъв голям обект или
масив/хеш от обекти от сървъра (обикновено сериализиран в JSON или XML) и го инстанцира като нов
локален обект от съответния език от страна на клиента, обикновено JavaScript/ECMAScript Този обект се
пази в паметта на браузъра и се използва многократно, с цел това да повиши скоростта при последващи
операции по четене от него. Ясно е че рядко може конвертираният обект в JavaScript да притежава 100%
от свойствата и методите на оригиналния обект, съставен от страна на сървъра на друг програмен език, а
и няма нужда от това. В случая, Proxy-то от страна на клиента е заместител на оригиналния обект и
обикновено се съставя така, че да има само свойствата (много рядко методи), нужни за прочитането и
представянето му в клиентския интерфейс.
Трябва да се отбележи, че демонстрираният по-горе начин на употреба на Proxy обекти служи по
два начина: първо - като заместител на оригиналния обект, поради невъзможност да се борави с него
директно, и второ - като филтър, който се прилага върху свойствата и методите на оригинала, така че да
останат само желаните/нужните такива.
Подобен на сценария с богатия потребителски интерфейс е използването на Proxy при
осъществяването на друг вид комуникация, характерна за днешния етап от развитието на мрежата и
услугите, които тя предлага. При осъществяване на комуникация чрез SOAP или подобен протокол,
независимо дали става въпрос за отдалечено извикване на метод/процедура или за пренос на друг вид
информация, често описателната сила на XML се използва за да може отсрещната страна на връзката да
може да изгради от сериализираното съдържание на съобщението сто процентов заместител на
оригинално изпратения обект.
Не на последно място, Proxy може да има приложение при нарочното имитиране на дадени обекти
от други “фалшиви“ такива. Този подход е особено популярен при unit-тестването и е известен като
mocking, като обектите заместители – Proxys – се наричат mock objects. Техниката се използва когато
трябва да се тества функционалността на даден метод, който приема като параметри и/или връща като
резултат някакви по-сложни обекти или зависи по някакъв друг начин от такива. Тъй като фокусът на
тестването е върху кода, който обработва информацията, а не върху самата информация или околна
среда, често е препоръчително въпросните обекти да бъдат заменени от „фалшификати“, които обаче да
споделят тези техни свойства и методи, които се използват от обработващия код, цел на тестването.
(Hunt, Ch.6)
Съществуват и структурни дизайн патерни, имащи цели сходни с тези на създаващите патерни,
касаещи създаването на нови обекти от даден тип и по-конкретно специализацията на тези обекти.
Такива патерни са Bridge и Decorator. И двата следващ обща цел да предотвратят създаването на
архитектура от класове, която в един момент става прекалено тежка и трудна за поддържане. Такива са
случаите, когато от даден първоначален абстрактен клас почнат да се родят множество унаследяващи го
такива, всеки от които с неговата си по-тясна специалност. Проблемът идва когато критериите за
наследяване поради специализация станат прекалено много и особено когато са на различни нива в
йерархията.
При Bridge целта е да се раздели абстракцията от имплементацията, тоест абстрактният клас, който
дефинира даден обект, да може да продължава да се разслоява (да бъде унаследяван и децата му да го
специализират в различни посоки), но в същото време имплементацията (реалният обект) да може също
да приема различни форми.
Примерът, който е илюстриран на следващите графики представлява онагледена ситуация, при
която даден базов клас трябва да може да бъде разделен на няколко подвида, но обект от всеки подвид
да може да бъде допълнително специализиран по определени свойства. В случая става въпрос за
решаване на проблем с изчисляването на цената на дадена напитка, плюс съответните възможни
добавки към нея, в кафене. Примерът и илюстрациите към него са взети от книгата ”Head First Design
Patterns” на издателство O'Reilly (Freeman, Ch.3).
Ето оригиналният базов абстрактен клас за напитка, плюс неговите конкретни имплементации –
подкласове. Основният метод който ни интересува е методът cost(), който връща цената на съответната
напитка.
(Freeman, p.80)
На следващата графика пък е илюстрирано какво се случва с тази засега спретната йерархия,
когато се наложи да се разслои според други свойства на обекта, описван от базовия клас. В случая,
всяка от четирите напитки може да бъде предложена с добавка: мляко, соево мляко, мока или бита
сметана. Комбинациите стават страшно много, поради възможните пермутации на добавките, умножени
по броя на напитките. Само част от възможните класове, които трябва да бъдат написани за подобен вид
специализация са представени на долната графика.
(Freeman, p.81)
В подобна ситуация, ако не се приложи решение от рода на Bridge се получават като краен ефект 1
+ m * n на брой класове, където 1 е оригиналният абстрактен клас, m е броят на класовете унаследяващи
го с цел определена специализация, а n е броят на класовете, унаследяващи го с цел конкретна
имплементация. Ако случаят е като горният, в който имплементациите n могат да се комбинират, тогава
формулата нараства до 1 + m * n!
Благодарение на Bridge, този брой може да бъде сведен до 1 + n + m. Любителите на алгоритмите,
а и не само те, веднага ще забележат огромната разлика в разряда. Количеството спестена работа по
разслояване и още по-важното – главоболието по поддръжка на подобна йерархия е огромно.
(Gamma, p.151)
Bridge архитектурата обаче има определени недостатъци – чрез нея няма да можем да постигнем
всички възможности, илюстрирани на горните графики с многото комбинации за напитките. Там може да
комбинираме само конкретна имплементация (например напитка с мляко) с конкретна специализация на
базовия клас (например напитката кафе), като заменим вътрешното свойство в конкретния дъщерен клас
с обект от въпросната имплементация. Тоест, възможно е да имаме само кафе или кафе с мляко, но не и
кафе с мляко и бита сметана. В някои случаи това ограничение е допустимо, в други не. За другите
случаи има съществуват съответните други решения.
Декораторът пък, още познат и като Wrapper (опаковка) също служи за решаване на проблем, при
който функционалността на даден обект трябва да може да се разширява, при това по време на
изпълнение на програмата, но без да е нужно да се създават n на брой допълнителни класове
унаследяващи базовия. При него различната имплементация на обект от общия базов клас се постига не
чрез наследяване на базовата имплементация, както при Bridge, чрез създаване на съвсем нова класова
йерархия, коренът на която има като свойство обект от базовия клас. Добавянето на функционалността
не става чрез наследяване от базовия клас на този обект, а чрез „обвиването“ на въпросния обект във
функции, които изменят базовата функционалност. Така вече се запазва не само възможността за
промяна по време на изпълнение на програмата, но и въпросната функционалност може да се обвие с
неограничен брой допълнителни такива чрез „обличане“.
Ето как изглежда принципната класова диаграма на декораторът:
(Gamma, p.175)
Декораторът би ни позволил да получим всичката желана свобода за получаването на желания
брой комбинации, без да е нужно да създаваме всичките онези класове. Нужно е просто да имаме един
базисен декоратор, съдържащ като свойство обект от даден тип напитка, и да създадем четири
наследяващи го декоратора за всеки тип добавка – мляко, соя, мока и бита сметана:
(Freeman, p.92)
Трикът се дължи на възможността на декорираните обекти да се съдържат един в друг и по този
начин да се комбинират свойствата на декораторите. Това многократно обвиване на на обектите един в
друг е възможно благодарение на общия им родител – абстрактния клас Beverage. Всеки дъщерен клас
притежава базисния метод cost(), но декориращите класове добавят към оригиналната функционалност
техните специфични промени, така че да „декорират“ обекта, според нуждата. Ето примерна илюстрация
на това как цената на напитката се променя, според това как е декорирана тя:
(Freeman, p.90)
Поведенчески дизайн патерни:
Поведенческите патерни имат за цел да идентифицират общи, често срещани начини/патерни на
комуникация между обектите и да ги реализират, за да се постигне по-гъвкава комуникация. Те касаят
алгоритмите и разпределянето на отговорностите между обектите. Целта е да се намали степента на
сложност в комуникацията между обектите и по този начин да се постигне и по-малка степен на
обвързаност между тях. Принципът на loose coupling гласи че колкото по-малко знаят един за друг
обектите и класовете, толкова по-добре, защото по този начин не разчитат експлицитно един на друг и
промените в тях са по-лесни за прилагане.
Observer е един от най-популярните и често прилагани поведенчески патерни още от зората на
обектно-ориентираното програмиране. Той е залегнал и в сърцевината на класическата MVC архитектура
(framework) на Smalltalk. Също познат като Publish-Subscribe, този патерн осъществява комуникацията
между обектите в не една и две архитектури. На практика, почти всяка библиотека за изграждане на
графична среда (пример: Java Swing, Qt, Delphi) използва вариации на този патерн за да имплементира
комуникацията между отделните си модули и обновяването на съдържанието на прозорците.
Основните играчи в схемата са Observer обектите (наблюдатели) и Subject обектите (субекти).
Обикновено субектите са обектите, съдържащи информацията която трябва да бъде представена, а
наблюдателите са отговорни за самото представяне, затова те зорко следят за промени в изходната
информация, които трябва да бъдат отразени. Ето примерна схема на ролите в едно графично
приложение за електронни таблици:
(Gamma, p.293)
Тази архитектура се реализира софтуерно посредством абстрактен клас Observer, абстрактен клас
Subject и неограничен брой унаследяващи ги конкретни имплементации. Основната функционалност,
залегнала в базисните класове, е следната: субектът трябва да има методи за прикачане и откачане на
наблюдатели – Attach( Observer) и Detach( Observer ), както и метод за известяване – Notify() - на промяна
във вътрешното му състояние, която е от интерес за наблюдателите. Закачайки и откачайки
наблюдатели, субектът поддържа вътрешен списък от такива, които изцикля при извикване на метода за
известяване, извиквайки методът за обновяване Update() на всеки един от наблюдателите. Този метод е
единствен за абстрактния клас Observer и също е абстрактен, тъй като конкретно какво се случва при
промяна зависи от конкретната имплементация на обекта-наблюдател. Класовете които унаследяват
Subject трябва да имат методи за присвояване и връщане на състоянието, което е от интерес на обектите
наблюдатели. Посредством тези методи, обектите от класовете които наследяват Observer осъществяват
функционалността на наследения абстрактен метод Update(); Хубавото на тази схема е че за субекта не
е нужно да знае нищо за наблюдателите, които го следят. Той просто е длъжен при промяна да извика
наследения си метод Notify(), който ще извърти абонираните наблюдатели, уведомявайки ги за промяната
чрез извикване на униформен метод.
Ето класовата диаграма на описаната горе конфигурация:
(Gamma, p.293)
Тъй като съвременните тенденции в уеб приложенията ги приближават все по-близко до локалните
приложения от гледна точна на потребителски интерфейс и усещане, няма нищо по-естествено от това
патерн като Observer, който се справя блестящо със задълженията си да регулира предаването на
съобщения в локалните графични приложения, да намери своето място в уеб приложенията. Той е
изключително удобен за осъществяването на комуникацията между уеб сървъра и браузъра с цел
опресняване на съдържанието, както и за комуникацията и синхронизацията между различни обекти,
находящи се в богатия потребителски интерфейс (AjaxPatterns, 1). Ще представя пример, в който се
демонстрира и от двата вида приложение:
Представете си богат потребителски интерфейс, който има за цел да представя на потребителя
визуализация на някакви актуални данни, които се четат от уеб сървъра и претендират за постоянна
актуалност, примерно графика на фиксинга на дадена валута или цена на акция. В идеалния случай, ако
това беше локално приложение, би било най-ефективно графиката на фиксинга да се променя при
настъпване на промяна в цената въпросната валута или акция. За съжаление, при уеб приложенията
сървъра няма инструмент и или способ, чрез който активно да се свърже с браузъра на клиента и да го
извести за промяната. Поради това ограничение подобни приложения поддържат отворена връзка от
браузъра към сървъра, по която регулярно да пускат заявки, чрез които да си опресняват данните. На
подобен принцип работят множество онлайн чат приложения или динамични табла за съобщения. Ако
въпросните данни са нещо по-сложно от обикновена числова стойност (би свършила работа за фиксинга),
то те обикновено се връщат от сървъра в сериализиран вид като JSON или XML, както при RSS feeds.
Когато върнатите данни представляват обект, обновен и изпратен от уеб сървъра, тогава често е добър
подход да се използва Proxy патерна, за да се направи огледален обект в скриптовия език от страна на
клиента. Този обект може да се реализира като наследник на Subject класа, така че да има методи Attach(
observer ), Detach( observer ) и Notify(), чрез които да може да приема, известява и премахва Observer
обекти, които се интересуват от промените в неговото състояние. От друга страна, може да има и други
субекти в JavaScript, от които зависи визуализацията на даден компонент, например входни елементи във
форма, указващи други параметри на споменатата графика на фиксинга, от рода на скала на деленията,
мерни единици, период и др. Те също би трябвало да се реализират като субекти, за които са абонирани
наблюдателите, които „рисуват“ графиката. Това е пример за browser-to-browser комуникация с
използване на Observer патерна.
При класическата реализация на Observer, каквато е демонстрирана в книгата на четиримата,
известяването на наблюдателите от страна на наблюдавания субект става чрез неговия метод Notify(),
който в същност се налага да извика еднаквият метод Update() на всеки от регистриралите се за него
наблюдатели. Този подход изисква минимално знание за структурата на наблюдаващите от страна на
наблюдавания, но все пак при JavaScript може да се подходи още по-хитро и вместо да абонираме
наблюдателите за субекта и той експлицитно да вика Update() в Notify() метода си, може просто да се
възползваме от вградената в DOM и JavaScript функционалност за настъпване, следене и обработка на
събития (events).
Още от времето на HTML 3 в по-напредналите браузъри съществува функционалност за следене на
определени DOM събития и извикването на скриптов код при настъпването им. В HTML DOM подобни
събития са onload, onunload, onclick, onfocus, onblur, onchange, onmouseover, onmouseout и др.
(W3Schools). Днес повечето, ако не всички, популярни библиотеки и frameworks за разширение на
вградената JavaScript функционалност (PrototypeJS, JQuery, Dojo) имат инструменти не само за по-
интелигентен начин за следене и улавяне на вградените DOM събития, отколкото използването на
гореспоменатите HTML тагове, но и предлагат на програмиста възможността да дефинира свои събития,
които после неговите обекти да предизвикват, а други методи и обекти да улавят. Тези собствени събития
носят семантиката на приложението.
Създаването и улавянето на специализирани събития позволява на програмиста да построи
архитектура на потребителския интерфейс, чрез Observer патерна, в която субектите просто трябва при
нужда да създадат (или жаргонно да „изстрелят“) съответното събитие, за което пък нужните
наблюдатели са абонирани. Абонирането може да става с помощта на специализиран клас – диспечер.
По този начин нашият прокси обект от страна на клиента бива обновяван чрез периодични обръщения
към уеб сървъра и при настъпила промяна спрямо предишното му състояние да изстрелва съответното
събитие, което ще причини абонираните за него наблюдатели да се само-обновят.
Както при стандартните обектно-ориентирани езици за програмиране, които стоят от страната на
сървъра, така и при скриптовите обектно-ориентирани езици от страна на клиента, какъвто е JavaScript,
запазването на капсулацията на данните (един от основните принципи на ООП) е нещо желано, което
гарантира висока гъвкавост и лесна поддръжка на приложението. И от двете страни прилагането на
Observer патерна спомага за това нивото на капсулация да не се нарушава от нуждата обектите, които
трябва да комуникират, да е нужно да знаят един за друг и да си викат експлицитно методите един
другиму.
Друг особено полезен и популярен стар патерн, който спомага както за запазването на
капсулацията, така и за изграждането на гъвкави и удобни приложения с потребителски интерфейси е
Memento. Както името му подсказва, той е свързан със запаметяване на определено състояние на даден
обект във времето, предаването на това състояние на друг обект, който се интересува то да бъде
запазено или възвърнато и самото действие на презареждане на първия обект в запомненото състояние
(своеобразно пътуване назад във времето). (Gamma. p.283)
Най-нагледният пример за употреба на мементо е приложение с потребителски интерфейс,
поддържащо обратимост и възвращаемост на действията на потребителя тип undo / redo. На повечето
напреднали (че и не дотам напреднали) потребители ни е трудно да си представим използването дори на
прост текстов редактор без употребата на вълшебната Ctrl + Z комбинация, да не говорим за по-сложни
приложения от рода на среди за разработка на програми или графични приложения. Следвайки
предначертаната вече пътека с аналогията между локалните и уеб-базираните приложения, обръщаме
внимание на тенденцията за постепенното премахване на границата между тях от гледна точка на
потребителя. Може смело да заявим че едно уважаващо себе си уеб приложение с достатъчно богат
потребителски интерфейс трябва да може да предложи на потребителя безпроблемна и лесна
обратимост на действията, подобно на тази с която е свикнал в локалните приложения. Това изискване е
още повече в сила когато става въпрос за някакъв вид редактиране на съдържание, като например в един
уеб-базиран HTML редактор или в приложение за управление на съдържанието на сайта, където
потребителят пренарежда и редактира дадени визуални модули.
Ето как изглежда общата клас диаграма на мементо патерна:
(Gamma. p.283)
В общи линии обектите които са обект на запазване на състоянието наследяват класа Originator,
който трябва задължително да има два публични метода – CreateMemento() и SetMemento( Memento ),
съответно за създаване на нова мементо репрезентация и за възвръщането на състоянието си от
подадена такава. Самият създаден мементо обект унаследява клас Memento, дефиниращ също два
публични метода: SetState() и GetState(). Те са огледални на тези в оригиналния обект, като биват
използвани съответно при създаването на ново мементо или при възстановяването на състоянието на
оригиналния обект от вече създаден мементо обект. Този обект също така има свойство state, описващо
именно запазеното статукво на originator-a в момента на създаването на мементо обекта. Трябва да се
обърне внимание, че този state далеч не е нужно и не е желателно да представлява всички свойства на
originator, а само тези които представляват интерес за трети обекти, които ще манипулират originator-a. В
това се състои свойството на мементо да запазва капсулацията на данните на оригиналните обекти,
разкривайки само това което е нужно (или в някои случаи нищо).
Самите мементо обекти са пасивни – не предизвикват никакви действия от само себе си. Те само
служат за да бъдат създавани от оригиналния обект, предавани на този който го интересува (обикновено
този който ще променя оригиналния обект или някой специализиран обект-пазител на състоянията) и
връщани обратно от трети страни на собственика, за да се възстанови от тях. Тази поредица от действия
е отразена на следващата диаграма:
(Gamma. p.283)
Нека видим как мементо помага за имплементацията на undo/redo функционалност в едно уеб
приложение от рода на система за управление на съдържанието. Представете си че имаме интерактивен
редактор на визуалните модули на сайта, в който чрез drag & drop аранжираме местоположенията на
отделните визуални модули, като тази конфигурация може да е различна за всяка една страница от
сайта. Приемаме че архитектурата на приложението е такава, че всичката интеракция между
потребителя и приложението става от клиентска страна с цел по-голяма динамика, без правене на връзка
към уеб сървъра преди финалното потвърждаване на промените. Също така приемаме че имаме един
обект (най-вероятно в JavaScript), който служи за структурно описание на редактираната от нас страница.
Освен информацията за това какви модули има визуализирани на тази страница, той пази и данни от
рода на съдържанието на страницата, нейното име и заглавие, HTML мета данни, данни за това кога е
създадена и последно променяна тази страница и др. Този обект служи за първоначалното рендиране на
редактора, като стартовото състояние отразява това на съответните данни записани от страна на уеб
сървъра. След зареждането почва редактирането, което, както казахме, се извършва само от страна на
клиента преди той да извърши действие за окончателно потвърждаване на промените.
Ето примерна структура на класовете на undo/redo системата:
Целта е да изградим undo/redo система, която да позволява на потребителя да отменя N на брой
стъпки назад направените от него действия и в случай че се откаже от отмяната да приложи същите
направени промени отново. За целта се изисква при всяка промяна да се извиква методът
registerChange() на UndoRedoSys, който от своя страна ще изиска от Page обекта да произведе мементо
(ще извика CreateMemento()), което мементо ще бъде съхранено в стека с промените на UndoRedoSys
обекта, като се обновява и индекс на това кой елемент от този стек отразява моментното състояние (в
нормално състояние, ако не е викано undo, последният + 1). След като тези действия са извършени
промените могат да бъдат приложени „на живо“ върху Page обекта. От гледна точна на хронологията на
потребителските действия, това се случва когато потребителя извлачи и пусне даден визуален модул в
ново местоположение, преди самото пускане на модула да „залепне“.
В последствие undo или redo функционалността се изпълнява при извикване на съответното
събитие (примерно натискане на бутон undo в интерфейса или на даден клавиш от клавиатурата, като
класическата Ctrl+Z комбинация). Тогава на база индекса на промените се взима предишният елемент от
стека с промените, който представлява мементо обект, и този мементо обект се предава на метода
SetMemento( memento ) на Page обекта, както в диаграмата по-горе. Page обекта обновява нужните
свойства от записаните в мементо обекта, след което може да бъде извикано рендирането на страницата
от променения Page обект. Redo се осъществява по аналогичен начин, само че чрез предвижване на
индекса на промените напред по стека, в случай че това е възможно.
Някой колега може би ще се запита защо просто не сериализираме обекта Page, описващ
състоянието на редактора в JSON, като пазим сериализираните версии в подходящ за това хеш или
масив във видимия обхват на адресното пространство в JavaScript или като свойство на UndoRedoSys.
Да, това определено ще свърши работа за запазването на състоянието и предаването на данните, но по
никакъв начин не спомага за другия положителен аспект от употребата на мементо, а именно запазването
на капсулацията. На UndoRedoSys обекта не му трябва да знае и да има всички свойства на Page
обекта. От гледна точка на ООП това има голяма стойност, дори при езици като JavaScript, в които няма
частни променливи на обектите. Именно поради факта, че няма подобно ограничение върху свойствата
на обектите и ценен механизмът на мементо, позволяващ в обекта който съхранява състоянието да
бъдат записани само тези свойства, които са от значение за възвръщането на старото състояние, а не
поголовно всички свойства на оригиналния обект. Не на последно място, това спомага и за намаляване
на нужния обем от памет за съхранение на запомнените състояния. Ако става въпрос за пазене на две-
три стъпки назад на състоянието на някакъв прост редактор с малък обем на редактираното съдържание
това не би имало особено значение, но при 200-300 стъпки назад, което не е много за някои приложения,
и голям обем данни в Page обекта, всеки спестен байт е от значение за скоростта на браузъра на
клиента.
Други видове софтуерни дизайн патерни
Освен програмните дизайн патерни има и други видове софтуерни дизайн патерни: архитектурни,
интерактивни, както и такива които са специфични за дадена категория софтуер. Архитектурните патерни
са се сдобили с може би най-голяма популярност, поради големия мащаб на техния ефект. Те описват
фундаменталната структурна схема на организацията на дадено софтуерно приложение. При тези
патерни обаче абстракцията е на толкова високо ниво, че за разлика от програмните патерни, между две
реализации на един и същ архитектурен патерн може да има огромни разлики. Въпреки това, те се
използват често за да дефинират основният характер на дадено софтуерно приложение, дори като
фактор за категоризация на софтуера в основни групи. Сред популярните архитектурни патерни са
разслоените приложения (Layers), N-tier, Peer-to-Peer, SOA (Service-oriented Architecture) и всемогъщия
MVC (Model-View-Controller), споменат неведнъж и в този документ. (Wiki, 4). Описанието на
архитектурните патерни, поради техния мащаб, е извън рамките и целите на настоящата работа, но
въпреки това се чувствам длъжен да отдам, макар и малко в телеграфен стил, нужното уважение на MVC
патерна.
MVC възниква като архитектура десетина години преди написването на книгата на „четиримата“ и
поради своя архитектурен характер не е категоризиран като патерн в нея, въпреки че е споменат
неведнъж. Неговият произход е свързан с възникването на обектно-ориентираните езици, по-специално с
първия им представител – Smalltalk. MVC е част от стандартна библиотека за създаване на приложения с
потребителски интерфейс на Smalltalk. Оригиналната му имплементация е описана в труда на Тригве
Рийнскауг (Trygve Reenskaug) “Applications Programming in Smalltalk-80: How to use Model–View–Controller“
през 1979г., който бързо става популярен в професионалните среди. (Wiki, 5) Основната идея на MVC е
отделянето на презентацията на данните от бизнес модела, който ги управлява, като остава един трети
компонент – контролерът – който до голяма степен менажира тази връзка между потребителя и бизнес
обектите.
研發替代役制度簡報
研發替代役制度簡報
研發替代役制度簡報
研發替代役制度簡報
研發替代役制度簡報
研發替代役制度簡報
研發替代役制度簡報

Más contenido relacionado

Similar a 研發替代役制度簡報

Mozllla Labs presentation
Mozllla Labs presentationMozllla Labs presentation
Mozllla Labs presentationBogomil Shopov
 
Google Cloud Natural Language for SEO
Google Cloud Natural Language for SEO  Google Cloud Natural Language for SEO
Google Cloud Natural Language for SEO Netpeak
 
Huseyin Ozbilen 41б_ 356291
Huseyin Ozbilen  41б_ 356291Huseyin Ozbilen  41б_ 356291
Huseyin Ozbilen 41б_ 356291yoska
 
John steinbeck teamwork_bg X607
John steinbeck teamwork_bg X607John steinbeck teamwork_bg X607
John steinbeck teamwork_bg X607X607
 
SEO курс 2014, лекция 4: Техническа оптимизация, част 2
SEO курс 2014, лекция 4: Техническа оптимизация, част 2SEO курс 2014, лекция 4: Техническа оптимизация, част 2
SEO курс 2014, лекция 4: Техническа оптимизация, част 2Lily Grozeva
 
Adobe flash
Adobe flash Adobe flash
Adobe flash farinaf1
 
Демо урок по програмиране със Светлин Наков
Демо урок по програмиране със Светлин НаковДемо урок по програмиране със Светлин Наков
Демо урок по програмиране със Светлин НаковSvetlin Nakov
 
Programming World in 2024
Programming World in 2024Programming World in 2024
Programming World in 2024Svetlin Nakov
 
3D4AUTO_R4_Virtual Seminars- BULGARIAN
3D4AUTO_R4_Virtual Seminars- BULGARIAN3D4AUTO_R4_Virtual Seminars- BULGARIAN
3D4AUTO_R4_Virtual Seminars- BULGARIAN3d4auto
 

Similar a 研發替代役制度簡報 (16)

Mozllla Labs presentation
Mozllla Labs presentationMozllla Labs presentation
Mozllla Labs presentation
 
Google Cloud Natural Language for SEO
Google Cloud Natural Language for SEO  Google Cloud Natural Language for SEO
Google Cloud Natural Language for SEO
 
Презентация ТЛК
Презентация ТЛКПрезентация ТЛК
Презентация ТЛК
 
Huseyin Ozbilen 41б_ 356291
Huseyin Ozbilen  41б_ 356291Huseyin Ozbilen  41б_ 356291
Huseyin Ozbilen 41б_ 356291
 
John steinbeck teamwork_bg X607
John steinbeck teamwork_bg X607John steinbeck teamwork_bg X607
John steinbeck teamwork_bg X607
 
SEO курс 2014, лекция 4: Техническа оптимизация, част 2
SEO курс 2014, лекция 4: Техническа оптимизация, част 2SEO курс 2014, лекция 4: Техническа оптимизация, част 2
SEO курс 2014, лекция 4: Техническа оптимизация, част 2
 
br4
br4br4
br4
 
Adobe flash
Adobe flash Adobe flash
Adobe flash
 
Web Pro CSS Intro
Web Pro CSS IntroWeb Pro CSS Intro
Web Pro CSS Intro
 
DHTML
DHTMLDHTML
DHTML
 
DHTML
DHTMLDHTML
DHTML
 
Демо урок по програмиране със Светлин Наков
Демо урок по програмиране със Светлин НаковДемо урок по програмиране със Светлин Наков
Демо урок по програмиране със Светлин Наков
 
Webloz2011
Webloz2011Webloz2011
Webloz2011
 
Programming World in 2024
Programming World in 2024Programming World in 2024
Programming World in 2024
 
Module1
Module1Module1
Module1
 
3D4AUTO_R4_Virtual Seminars- BULGARIAN
3D4AUTO_R4_Virtual Seminars- BULGARIAN3D4AUTO_R4_Virtual Seminars- BULGARIAN
3D4AUTO_R4_Virtual Seminars- BULGARIAN
 

Más de Mu Chun Wang

如何在有限資源下實現十年的後端服務演進
如何在有限資源下實現十年的後端服務演進如何在有限資源下實現十年的後端服務演進
如何在有限資源下實現十年的後端服務演進Mu Chun Wang
 
深入淺出 autocomplete
深入淺出 autocomplete深入淺出 autocomplete
深入淺出 autocompleteMu Chun Wang
 
你畢業後要任職的軟體業到底都在做些什麼事
你畢業後要任職的軟體業到底都在做些什麼事你畢業後要任職的軟體業到底都在做些什麼事
你畢業後要任職的軟體業到底都在做些什麼事Mu Chun Wang
 
網路服務就是一連串搜尋的集合體
網路服務就是一連串搜尋的集合體網路服務就是一連串搜尋的集合體
網路服務就是一連串搜尋的集合體Mu Chun Wang
 
老司機帶你上手 PostgreSQL 關聯式資料庫系統
老司機帶你上手 PostgreSQL 關聯式資料庫系統老司機帶你上手 PostgreSQL 關聯式資料庫系統
老司機帶你上手 PostgreSQL 關聯式資料庫系統Mu Chun Wang
 
使用 PostgreSQL 及 MongoDB 從零開始建置社群必備的按讚追蹤功能
使用 PostgreSQL 及 MongoDB 從零開始建置社群必備的按讚追蹤功能使用 PostgreSQL 及 MongoDB 從零開始建置社群必備的按讚追蹤功能
使用 PostgreSQL 及 MongoDB 從零開始建置社群必備的按讚追蹤功能Mu Chun Wang
 
Funliday 新創生活甘苦談
Funliday 新創生活甘苦談Funliday 新創生活甘苦談
Funliday 新創生活甘苦談Mu Chun Wang
 
大解密!用 PostgreSQL 提升 350 倍的 Funliday 推薦景點計算速度
大解密!用 PostgreSQL 提升 350 倍的 Funliday 推薦景點計算速度大解密!用 PostgreSQL 提升 350 倍的 Funliday 推薦景點計算速度
大解密!用 PostgreSQL 提升 350 倍的 Funliday 推薦景點計算速度Mu Chun Wang
 
如何使用 iframe 製作一個易於更新及更安全的前端套件
如何使用 iframe 製作一個易於更新及更安全的前端套件如何使用 iframe 製作一個易於更新及更安全的前端套件
如何使用 iframe 製作一個易於更新及更安全的前端套件Mu Chun Wang
 
pppr - 解決 JavaScript 無法被搜尋引擎正確索引的問題
pppr - 解決 JavaScript 無法被搜尋引擎正確索引的問題pppr - 解決 JavaScript 無法被搜尋引擎正確索引的問題
pppr - 解決 JavaScript 無法被搜尋引擎正確索引的問題Mu Chun Wang
 
模糊也是一種美 - 從 BlurHash 探討前後端上傳圖片架構
模糊也是一種美 - 從 BlurHash 探討前後端上傳圖片架構模糊也是一種美 - 從 BlurHash 探討前後端上傳圖片架構
模糊也是一種美 - 從 BlurHash 探討前後端上傳圖片架構Mu Chun Wang
 
Google Maps 開始收費了該怎麼辦?
Google Maps 開始收費了該怎麼辦?Google Maps 開始收費了該怎麼辦?
Google Maps 開始收費了該怎麼辦?Mu Chun Wang
 
Git 可以做到的事
Git 可以做到的事Git 可以做到的事
Git 可以做到的事Mu Chun Wang
 
那些大家常忽略的 Cache-Control
那些大家常忽略的 Cache-Control那些大家常忽略的 Cache-Control
那些大家常忽略的 Cache-ControlMu Chun Wang
 
如何利用 OpenAPI 及 WebHooks 讓老舊的網路服務也可程式化
如何利用 OpenAPI 及 WebHooks 讓老舊的網路服務也可程式化如何利用 OpenAPI 及 WebHooks 讓老舊的網路服務也可程式化
如何利用 OpenAPI 及 WebHooks 讓老舊的網路服務也可程式化Mu Chun Wang
 
如何與全世界分享你的 Library
如何與全世界分享你的 Library如何與全世界分享你的 Library
如何與全世界分享你的 LibraryMu Chun Wang
 
如何與 Git 優雅地在樹上唱歌
如何與 Git 優雅地在樹上唱歌如何與 Git 優雅地在樹上唱歌
如何與 Git 優雅地在樹上唱歌Mu Chun Wang
 
API Blueprint - API 文件規範的三大領頭之一
API Blueprint - API 文件規範的三大領頭之一API Blueprint - API 文件規範的三大領頭之一
API Blueprint - API 文件規範的三大領頭之一Mu Chun Wang
 
手把手教你如何串接 Log 到各種網路服務
手把手教你如何串接 Log 到各種網路服務手把手教你如何串接 Log 到各種網路服務
手把手教你如何串接 Log 到各種網路服務Mu Chun Wang
 

Más de Mu Chun Wang (20)

如何在有限資源下實現十年的後端服務演進
如何在有限資源下實現十年的後端服務演進如何在有限資源下實現十年的後端服務演進
如何在有限資源下實現十年的後端服務演進
 
深入淺出 autocomplete
深入淺出 autocomplete深入淺出 autocomplete
深入淺出 autocomplete
 
你畢業後要任職的軟體業到底都在做些什麼事
你畢業後要任職的軟體業到底都在做些什麼事你畢業後要任職的軟體業到底都在做些什麼事
你畢業後要任職的軟體業到底都在做些什麼事
 
網路服務就是一連串搜尋的集合體
網路服務就是一連串搜尋的集合體網路服務就是一連串搜尋的集合體
網路服務就是一連串搜尋的集合體
 
老司機帶你上手 PostgreSQL 關聯式資料庫系統
老司機帶你上手 PostgreSQL 關聯式資料庫系統老司機帶你上手 PostgreSQL 關聯式資料庫系統
老司機帶你上手 PostgreSQL 關聯式資料庫系統
 
使用 PostgreSQL 及 MongoDB 從零開始建置社群必備的按讚追蹤功能
使用 PostgreSQL 及 MongoDB 從零開始建置社群必備的按讚追蹤功能使用 PostgreSQL 及 MongoDB 從零開始建置社群必備的按讚追蹤功能
使用 PostgreSQL 及 MongoDB 從零開始建置社群必備的按讚追蹤功能
 
Funliday 新創生活甘苦談
Funliday 新創生活甘苦談Funliday 新創生活甘苦談
Funliday 新創生活甘苦談
 
大解密!用 PostgreSQL 提升 350 倍的 Funliday 推薦景點計算速度
大解密!用 PostgreSQL 提升 350 倍的 Funliday 推薦景點計算速度大解密!用 PostgreSQL 提升 350 倍的 Funliday 推薦景點計算速度
大解密!用 PostgreSQL 提升 350 倍的 Funliday 推薦景點計算速度
 
如何使用 iframe 製作一個易於更新及更安全的前端套件
如何使用 iframe 製作一個易於更新及更安全的前端套件如何使用 iframe 製作一個易於更新及更安全的前端套件
如何使用 iframe 製作一個易於更新及更安全的前端套件
 
pppr - 解決 JavaScript 無法被搜尋引擎正確索引的問題
pppr - 解決 JavaScript 無法被搜尋引擎正確索引的問題pppr - 解決 JavaScript 無法被搜尋引擎正確索引的問題
pppr - 解決 JavaScript 無法被搜尋引擎正確索引的問題
 
模糊也是一種美 - 從 BlurHash 探討前後端上傳圖片架構
模糊也是一種美 - 從 BlurHash 探討前後端上傳圖片架構模糊也是一種美 - 從 BlurHash 探討前後端上傳圖片架構
模糊也是一種美 - 從 BlurHash 探討前後端上傳圖片架構
 
Google Maps 開始收費了該怎麼辦?
Google Maps 開始收費了該怎麼辦?Google Maps 開始收費了該怎麼辦?
Google Maps 開始收費了該怎麼辦?
 
Git 可以做到的事
Git 可以做到的事Git 可以做到的事
Git 可以做到的事
 
那些大家常忽略的 Cache-Control
那些大家常忽略的 Cache-Control那些大家常忽略的 Cache-Control
那些大家常忽略的 Cache-Control
 
如何利用 OpenAPI 及 WebHooks 讓老舊的網路服務也可程式化
如何利用 OpenAPI 及 WebHooks 讓老舊的網路服務也可程式化如何利用 OpenAPI 及 WebHooks 讓老舊的網路服務也可程式化
如何利用 OpenAPI 及 WebHooks 讓老舊的網路服務也可程式化
 
如何與全世界分享你的 Library
如何與全世界分享你的 Library如何與全世界分享你的 Library
如何與全世界分享你的 Library
 
如何與 Git 優雅地在樹上唱歌
如何與 Git 優雅地在樹上唱歌如何與 Git 優雅地在樹上唱歌
如何與 Git 優雅地在樹上唱歌
 
API Blueprint - API 文件規範的三大領頭之一
API Blueprint - API 文件規範的三大領頭之一API Blueprint - API 文件規範的三大領頭之一
API Blueprint - API 文件規範的三大領頭之一
 
Git 經驗分享
Git 經驗分享Git 經驗分享
Git 經驗分享
 
手把手教你如何串接 Log 到各種網路服務
手把手教你如何串接 Log 到各種網路服務手把手教你如何串接 Log 到各種網路服務
手把手教你如何串接 Log 到各種網路服務
 

研發替代役制度簡報

  • 1. Software Design Patterns: Минало, Настояще и Бъдеще Въведение Понятието дизайн патерн (design pattern), що се касае до софтуерното инженерство, се дефинира като общо решение за често срещан проблем, което може да бъде използвано многократно (Wiki, 1). Не е учудващо че тази дефиниция най-вероятно може да бъде приложена към всяка една друга сфера на организирана дейност. Това е защото идеята е стара, колкото и популярният израз за това, че е не нужно човек да открива топлата вода (или колелото, или Америка) – това вече е направено отдавна. Този израз най-добре носи посланието на настоящия документ, както и на самата идея за дизайн патерните. Преди да премина към кратка история на зараждането и формалната употреба на термина, бих желал да направя кратко речниково уточнение касаещо понятието дизайн патерн(и) и многократната му употреба в този документ именно в оригинал, макар и на кирилица, подобно на добре установила се чуждица в езика ни. В целия документ този термин се среща в оригинал, поради убедеността ми в невъзможността за точен превод на български език. Преводът е възможен, но всяка вариация го отдалечава в една или друга степен от оригиналното значение. Първата дума - дизайн – отдавна присъства като чуждица в българския език и се е наложила като една от най-често употребяваните думи за описване на творческата дейност по създаването на някакъв нов продукт. Тъй като така и така тази чуждица си е извоювала мястото в езика ни, не смятам че употребата й представлява проблем. Втората дума обаче е главният виновник за това терминът да се използва предимно в оригинал. Думата „pattern“ има много възможни преводи в българския език, според контекста на употребата, но нито един не е 100% точен. Сред най-популярните са модел, шаблон, скица и др. Нито една от тези думи не носи обаче общия смисъл на думата в оригинал извън контекста. Търсенето на патерн примерно в поведението на даден човек, както профайлърите от ФБР правят, най- точно се определя като търсене на закономерност, на повтарящи се елементи, на модел на поведение, но нито една от тези думи пък не е подходяща при употребата на термина в софтуерното инженерство, още по-малко пък в общия смисъл на думата. Затова, за да не развалям цялото изложение с комични термини от рода на небезизвестната драсни-пални-клечица, реших да запазя оригинала и да го употребявам непроменен. В съвременната наука терминът се налага и добива популярност благодарение на убежденията, книгите и работата на архитектът Кристофър Александър през 70-те години на XX в. Той е бил убеден че решаването на класически човешки нужди чрез съответните архитектурни решения е допринесло за сътворяването на всички велики архитектурни шедьоври от миналото. За него успешните сгради са тези, чрез които архитекта посреща нуждите на хората, които ги ползват, и това ги прави красиви и хармонични. Акцентира се на ролята на потребителя на сградата, като определящ нейната функция и от
  • 2. там съответно на архитектурата, която има за цел да отговори на тази нужда. Кристофър Александър е вярвал в почти мистичният смисъл на думата в това че съществува вечен, неподвластен на времето стил или начин за строене, който се крие в спазването на гореспоменатите правила. (Wiki, 2) За да стигне по-близко до дефинирането на този процес, Кристофър Александър написва няколко книги, една от които е “A Pattern Language: Towns, Buildings, Construction“, в която се опитва да дефинира именно тези повтарящи се решения за многократната употреба на общи нужди в архитектурата, които определят полезността и от там красотата на сградата. Идеята в тези негови трудове, както и систематичността на изложените примери вдъхновяват през 1987г. Кент Бек и Уард Кънингам да пробват да намерят дизайн патерни в програмирането. Публикуваните от тях резултати запалват и други колеги от софтуерния бранш да опитат същото. Най-известната книга по тематиката е “Design Patterns: Elements of Reusable Object-Oriented Software“, чиито автори са Ерих Гама, Ричард Хелм, Ралф Джонсън и Джон Влисидес, по известни под прозвището „бандата на четиримата“. Книгата е издадена през 1994г. и описва 23 класически софтуерни дизайн патерни с примери на C++ и Smalltalk. Те са изложени в своеобразен каталог, според функцията която изпълняват, на Creational (създаващи), Structural (структурни) и Behavioral (поведенчески) патерни. Всеки патерн е документиран с точно определен брой характеристики, които помагат на програмиста да определи дали именно този патерн е най-подходящият за решение на настоящия проблем. (Gamma, ch.III) Освен споменатата каталожна част с детайлната категоризация и примери за всеки патерн, книгата на „четиримата“ може да се похвали и с две много добри въвеждащи глави. Първата има за цел да аргументира употребата на дизайн патерни, като разясни предимствата на този вид работа и даде насоки за ползотворното им приложение (Gamma, ch.I). Някои от тези насоки са отразени и в настоящия документ. Втората част включва нагледни примери за изграждането на различни приложения посредством дизайн патерни, като това позволява на читателя да види именно как даден дизайн патерн влиза в употреба и така да се каже си намира мястото в дадено софтуерно решение (Gamma, ch.II). От издаването на тази книга до днес са минали почти 15 години. Когато книгата е писана не само че не е имало уеб 2.0, gmail.com, facebook, но и добрия стар уеб 1.0 едва е прохождал, а PHP (тогава още без версия) е представлявало скромна колекция от малки CGI бинарни програмки, написани на С, изпълняващи ролята на лични Personal Home Page Tools на Размус Лердорф (Wiki, 3). Това обаче по никакъв начин не променя стойността на отразените в нея решения. Това се дължи на няколко причини: Както Кристофър Александър е забелязал що са касае до архитектурата, добрите решения са полезните решения и полезните решения са вечни. Основните архитектурни проблеми на софтуерната архитектура, пред които са поставени програмистите, принципно са едни са едни и същи, независимо дали говорим за локално приложение на C или за разпределена уеб-базирана система. Инструментите, платформите и програмните езици се развиват, но проблемите остават изконно същите, за разлика от инструментариума. Така или иначе, основната бизнес логика на уеб приложенията, в случаите когато са написани кадърно и с поглед в бъдещето, се съдържа в набор от моделни класове, които нямат нищо общо с уеб
  • 3. характера на приложението. Тези структури от данни, обслужващи бизнес модела, независимо от езика на който са писани, не се различават по функция от тези които са обслужвали същите бизнес нужди, но по друг, не уеб-базиран начин преди 15 години. Още повече, че в днешни дни AJAX технологията приближава уеб приложенията все повече до локалните софтуери, като приложението на дизайн патерните се простира отвъд бизнес модел класовете и достига богатия потребителски интерфейс от страна на клиента, както и комуникацията между него и сървъра. Това, както ще видите по-напред в изложението, е видно и в зараждането на днешните AJAX патерни, които в своята основа често повтарят или имитират класически дизайн патерни, споменати в книгата на „четиримата“. Характеристики и Класификация на Дизайн Патерни Въпреки че няма универсален стандартен начин по който да се документират дизайн патерни, форматът на свойствата им, наложен от „четиримата“, е най-близкото до стандарт за описване на дизайн патерн или поне е достатъчно меродавно, че авторите след тях да се водят по него. Тези основни свойства, на които авторите дават стойност за всеки патерн са: − Име и класификация: Уникално име, служещо за идентификация на патерна − Замисъл (intent): Описание на целта на въпросния патерн и обуславяне на причината да се ползва − Синоними: Други наименования, под които е познат същия патерн − Мотивация: Примерен сценарий, представящ проблем и контекст, в който патерна може да бъде от полза − Приложимост: Ситуации, в които е приложим патерна – помагат да изясняване на контекста − Структура: Графично представяне на патерна, обикновено посредством употребата на UML средства, като Class и Interaction диаграми. − Участници: Списък с всички класове и обекти, използвани в патерна с техните роли в софтуерния дизайн − Сътрудничество (collaboration): Описание на това как класовете и обектите използвани в патерна си взаимодействат − Последствия: Описание на резултатите, страничните ефекти и недостатъците свързани с употребата на този дизайн патерн − Имплементация: Описание на имплементацията на патерна, на реалното решение − Примерен код: Примерна употреба на патерна, реализирана на конкретен програмен език − Познати употреби: Примери на реална употреба на патерна в съществуващи решения − Свързани патерни: Други патерни, които имат връзка с настоящия. Обикновено се правят сравнения
  • 4. между него и изброените. (Gamma, Ch. 1) Тази детайлна схема на описание на даден патерн в по-голяма или по-малка степен е взаимствана от други автори от излизането на гореупоменатата книга до наши дни. Пример за това е сайтът ajaxpatterns.org (ще бъде разгледан по-подробно по-натам в изложението), посветен на събирането и категоризиране на добри практики и дизайн патерни при реализирането на AJAX-базирани уеб приложения. Въпреки че някои от наименованията на свойствата се различават и броят им не е 100% същия, принципно те са доста близко до оригиналните. Това също илюстрира големия ефект, който книгата на „четиримата“ оказва и до днес в професионалните среди. Както вече бе споменато, патерните изброени и описани в каталожната част от книгата са разделени на Creational (създаващи), Structural (структурни) и Behavioral (поведенчески) патерни. Тези категории са условни, според основната функция на патерните, но много по-сетнешни автори въвеждат допълнителни такива, обикновено специфични за даден по-специален вид приложения. Примери за такива категории са: − Concurrency: съгласуващи или успоредни – касаещи много-процесните и много-нишкови приложения − Distributed: разпределени – касаещи софтуерни решения, разпределени да работят на повече от един компютър − Usability: касаещи потребителското взаимодействие със системата Също така, има една по-горна категория софтуерни дизайн патерни, която обхваща не решаването на конкретен проблем, а цялостната архитектура на дадено софтуерно приложение. Това е групата на архитектурните патерни. Пример за такива са небезизвестният MVC (Model-View-Controller – ще бъде разгледан по-късно), Three-Tier, Pipeline, Peer-to-Peer, Service-Oriented Architecture, Layers и др (Wiki, 4). Съвременните уеб-базирани приложения, освен базисните създаващи, структурни и поведенчески части, често имат елементи и от разпределените системи, проблеми свързани с потребителското взаимодействие, както и дори понякога успоредно протичащи процеси (особено често при скриптовите езици от страна на клиента). Добре избраната архитектура на цялото приложение неминуемо играе огромна роля за ефективността и гъвкавостта му. Следователно, полезните дизайн патерни за едно уеб приложение трябва да се търсят в почти всички известни категории такива. Започвайки от класическите три категории (Creational, Structural, Behavioral) и разпростирайки се върху още няколко, ще се опитам да направя едно полезно изложение на избрани дизайн патерни, които са незаменимо полезни при разработката на едно добро функционално и гъвкаво съвременно уеб приложение.
  • 5. Създаващи (creational) дизайн патерни: Групата на създаващите патерни включва такива, които се отнасят до създаването на нови обекти – механизмите които се използват за да се създават те по най-подходящия за ситуацията начин. Те се използват, когато получаването на обект от даден клас по класическия начин – чрез инстанциране на нов обект – не е най-подходящия начин, поради някакви причини. Пример за това е създаването на обект от клас, който представлява абстракция за връзка към база данни. Ако при всяко създаване на нов такъв обект се отваря нова връзка към базата, то прекомерната употреба по този начин би довела до бързо изразходване на позволения от сървъра брой връзки към базата. В това отношение няма сериозна разлика между приложенията от преди 15 години и тези сега – всички те ползват от време на време някакви външни ресурси, било то файлове, потоци, или бази данни, като достъпът до тези ресурси е ограничен и трябва да се пести и да се пази от претоварване. Създаващите патерни предлагат гама от решения на този вид проблеми посредством патерните Factory Method, Factory, Abstract Factory, Object Pool и Singleton. Има и други създаващи дизайн патерни, като например Builder, Prototype и Lazy Initialization, но целта ми не е да разгледам всеобхватно всички, а да фокусирам върху тези от тях които са особено полезни и ключови при създаването на уеб приложения. В основата на решението на проблема с нежелания прекомерен достъп стои идеята даден споделен общ за целия софтуер ресурс да бъде достъпван чрез един единствен обект, за да се избегне нежелана дупликация или претоварване. Това се осъществява обикновено посредством Singleton. Singleton се използва за ограничаване употребата на даден клас до един единствен обект. Обяснено с по- прости думи, Singleton може да бъде разглеждан като едно по-културно и мащабируемо решение – еквивалент на иначе простата глобална променлива, която бихте могли да инициализирате в някаква обща за целия софтуер начална точка и да ползвате като глобална в последствие където е необходима. Освен че се придържа към ООП стила на програмиране и капсулира данните, не „замърсявайки“ общото пространство с допълнителни данни, Singleton превъзхожда глобалната променлива по това че не е нужно да помните, че трябва да го инициализирате в някаква начална точка, предхождаща първата му употреба. Както ще стане очевидно от примера по-долу, той просто се инициализира сам при първата си употреба и тази същата инстанция се ползва при всички последващи употреби. Това също е плюс в случаите когато изобщо не се стигне до употреба – тогава просто не се случва никакво инстанциране на въпросния обект. Следва пример за употребата на глобална променлива като носител на обект за връзка с база данни и пример за употребата на Singleton за същата цел: $db_conn = new DBConnection( $db_name ); . . //Later in some other file class X { ... function methodX1() { global $db_conn;
  • 6. $res = $db_conn­>query( 'SELECT * FROM t1' ); ... } function method X2() { global $db_conn; $res = $db_conn­>query( 'SELECT a, b FROM t2' ); ... } ... } А ето как изглежда същият пример с употребата на Singleton: require 'DBSingleton.class.php' ... // ­        По натам в някой друг файл class X { ... function methodX1() { $db_conn = DBSingleton::getInstance(); $res = $db_conn­>query( 'SELECT * FROM t1' ); ... } function method X2() { $db_conn = DBSingleton::getInstance(); $res = $db_conn­>query( 'SELECT a, b FROM t2' ); ... } ... } Ето и самият Singleton клас, унаследяващ оригиналния клас за базата данни, като това е само един вариант, не е задължително да ставя чрез унаследяване: class DBSingleton extends DBConnection{ private static $instance; protected final function __construct( $db_name ) { parent::__construct( $db_name ); } public static function getInstance() {      if( !self::$instance instanceof self ) self::$instance = new self; return self::$instance;
  • 7. } } Поредното предимство, което авторите на ”Design Patterns...” книгата изтъкват е, че ползването на глобалната променлива, макар да е гаранция за достъпност на въпросния един обект, не е гаранция за това че не може да бъдат създадени още от този тип. Ограничаването на създаването на нови обекти от дадения тип чрез Singleton е доста по-надежден начин да се предпази кода от нежелани допълнителни инстанцирания. Това може да се гарантира допълнително, ако оригиналният DBConnection клас се дефинира като abstract, за да не може да се извика директно конструкторът му. (Gamma, p.127) Singleton е един от най-простите от гледна точка на брой използвани класове и обекти патерн – той съдържа само един клас и един обект в него. Това го прави удобен като градивен елемент на по-сложни създаващи патерни. Типичен ползвател на Singleton е Abstract Factory патернът. Идеята на Abstract Factory е да предложи на кода-клиент достъп до набор от конкретни „фабрики“, които произвеждат даден тип обекти. В тази концепция отново е заложена идеята, както в Singleton, че понякога създаването на нови обекти от даден тип по класическия начин с new не е най-доброто решение. Докато при Singleton това обикновено се дължи на нежеланието да се прекалява със създаването на нови обекти от дадения тип, то при фабриките причината по-скоро се крие в „незнанието“ за това какъв конкретно обект от даден тип му трябва на кода ползвател, за да си свърши работата. По този начин софтуерният архитект може да създаде гъвкави решения за дадени категории проблеми, без да трябва да задълбава в конкретни типове. Тук се разчита на общия интерфейс на всички обекти, споделящи някакъв по-горен общ родителски клас. (Gamma, p.87) Горната схема е взета от примерите за употреба на Abstract factory книгата на „четиримата“, като конкретният пример касае създаване на архитектура от класове, обслужваща графично приложение, като
  • 8. своеобразната библиотека за графични компоненти може да обслужва повече от една платформа за визуализация. Прилагайки примера към нуждите на съвременните уеб приложения, може да го сравним със сходна нужда – създаването на JavaScript библиотека за визуални компоненти от рода на кутии със съобщения или грешки, диалогови прозорци или кутии за потвърждение или отхвърляне на дадено действие и др. подобни. Целта на библиотеката обаче, освен функционалността на тези елементи, е да предостави на потребителите и добър външен вид на елементите, като този външен вид може да се настрои да приеме една от няколко визуални теми, например в стил Windows или Mac OS X. Кодът който ползва елементите не трябва да се интересува от настройката за стил на визуализация, а да се обръща към всички обекти произведени от дадена абстрактна фабрика ползвайки еднакъв споделен интерфейс, независимо от визуалната им специфика: Въпреки че употребата на Singleton не е задължително условие за имплементацията на Abstract Factory, то определено е едно от популярните и ефективни такива. Конкретните фабрики, които абстрактната фабрика ползва, са подходящи кандидати за Singletons, поради това че не е нужно да се създава повече от една от даден тип, а и възможността някои от тях да не бъдат създадени изобщо, ако не възникне нужда от обект, който те да произвеждат. Друг много популярен похват/патерн в софтуерното инженерство, често реализиран посредством Singleton, е Object Pool. От една гледна точка, Object Pool може да бъде разглеждан като разширена версия на Singleton, в която достъпът до въпросният контролиран обект от даден тип е ограничена не до един-единствен обект, а до набор от определен брой такива, които всеки може да взима за ползване и после да връща, подобно на книги от кварталната библиотека. Говорейки с конкретни примери, пак може да става въпрос за достъп до база данни или сокети, файлове, отворени и логнати FTP връзки или какъвто и да било друг ресурс (обикновено външен), който е „скъп“ от гледна точка на памет или време за създаване и за това е желателна многократната му употреба преди да бъде разрушен, за да се спести
  • 9. времето за повторното му създаване. Ето една примерна реализация на Object Pool на PHP5, която реализира достъп до отворени FTP връзки към даден сървър. За примера се приема, че правим връзки към един и същ отдалечен FTP сървър и че няма да се интересуваме от предаване на потребителски имена или пароли (FTPConn класа да му мисли – приемаме че си има свои начини да прочете тази конфигурационна информация от някъде). Всяка отворена връзка също така има валидност във времето, съобразена с предполагаемия (или известен) размер на времето след което отсрещният сървър ще затвори връзката заради неактивност. Object pool-ът следи тази валидност, за да не се случи да предостави на „клиентите“ си изтекла връзка. Приема се, също така, че имаме клас на име FTPConn, обектите от който осъществяват свръзката и FTP операциите. static class FTPPool{ private static $ftp_conns = array(); public static function getConn() {      if( count( self::$ftp_conns ) ) { do{ $ftp = array_shift( self::$ftp_conns ); } //May add more conditions for expiring connections, like time while( $ftp != null && !$ftp­>isConnected() );  if( $ftp ) return $ftp; } $ftp = new FTPConn(); $ftp­>connect(); return $ftp; } public static function returnConn( $conn ) { //Reset any state­related info here, if needed, reset clocks, if any return  array_push( self::$ftp_conns, $conn ); } } Както е видимо, самият Object Pool е Singleton – статичен е и достъпът до него се осъществява посредством статични методи, които манипулират вътрешен статичен масив от активни, изградени връзки. Първият пък когато някой код-клиент на Object Pool-a извика FTPPool::getConn()    той ще създаде нов обект (и ще го закачи), защото вътрешният му масив ще е празен. Следващият път, когато този метод бъде извикан, ако първата създадена връзка-обект още не е върната посредством FTPPool::retunConn( $conn ),  ще продължат да се създават нови връзки, защото другите още са в успоредна употреба. В момента който употребените обекти-връзки почнат да се връщат, вътрешният масив ще почне да се пълни, след като рециклира всяка една от тях, ако това е нужно. Тогава вече, getConn() ще почне да връща от рециклираните, при положение че те все още са закачени успешно за
  • 10. отдалечения FTP сървър. Тук е моментът да се упомене, че Object Pool-a може да налага още повече контрол върху броя и качеството на връщаните от и към него обекти. Може да се наложи максимален размер на pool-a, който да се следи да не бъде нарушаван (да почне да хвърля exceptions, например при поискване на поредния обект, който вече е с един повече от допустимото), може активно да рециклира статуса на върнатите обекти, спрямо някакъв еталон, може също така и да наложи трайност на обектите в pool-a, така че да не връща обекти които са изтекъл “срок на годност”. Възможностите са много, нуждата се определя от характера на обектите, които биват обслужвани. Също така е честа разновидност Object Pool-а е моделът, чийто pool представлява всъщност двумерен масив от pools, разделени по типове според ключа. Например, ако нашият object pool от примера трябва да обслужва N на брой FTP сървъри, може хост името на дадения FTP сървър да се ползва като ключ към масивът от обекти-връзки към въпросния сървър. Връзките към отдалечени сървъри (и като цяло I/O операциите) се смятат като цяло за едни от частите на приложението, които най-много допринасят за забавяне на изпълнението, но далеч не са единствените обекти, които си струва да бъдат извличани от Object Pool. В едно приложение с много потребители и като цяло много изпълнения на даден скрипт за единица време, дори един по-тежък като структура обект с доста свойства може да си струва да бъде извличан посредством Object pool, при положение че характера му и архитектурата на приложението го позволява. Последният от създаващите патерни, който искам да разгледам е Prototype, макар и не толкова детайлно колкото предишните. Prototype също работи в посока да не се създават новите обекти от даден тип посредством new оператора, но той стига още по-далече от Singleton и Object Pool, като цели дадени класове да се ползват за създаването на само един обект (x) от всеки клас (ClassX) и после всяка следваща инстанция от този клас да се създава не чрез извикване на new ClassX, а чрез клониране на вече създаденият прототипен обект x. За разлика от Singleton и Object Pool, Prototype няма за цел да ограничава броя на създадените обекти от даден клас, той просто спестява използването на new за тази цел, като го заменя с клониране на съществуващ прототипен обект. Подобен подход е подходящ за създаване на обекти, които могат да имат само няколко дискретни състояния. Тогава може просто да се създават чрез клониране от обект-прототип в желаното състояние. Интересно е да се отбележи, че има обектно-ориентирани езици за програмиране, в които създаването на нови обекти изцяло се осъществява чрез prototyping. Такива са Self, Omega, както и добрия стар познат на всички JavaScript / ECMAScript. В тези езици унаследяването става чрез създаване на нов обект, клониран от даден родител-прототип, като на новосъздадения клонинг динамично се добавят нови свойства и методи. В езиците в които пък няма loose-typing, Prototype е удобен патерн за имплементация на полиморфизъм. Тогава от базовия клас се създават различни обекти-прототипи, които, посредством специален клас-мениджър на прототипите, пък служат за клониране на нови обекти, които пък от своя страна вече имат различна имплементация на базовата функционалност. Това допринася за динамиката на приложението в run-time, която по принцип е по-слабо изразена при strong-typing езиците.
  • 11. Структурни дизайн патерни: Структурните патерни се занимават с комбинирането на класове или обекти с цел съставяне на по- мащабни структури. Тези патерни условно се делят на такива които касаят класове и такива които касаят обекти. Структурните патерни за класове помагат за изграждането на интерфейси или имплементации, съставени от повече от един под-класове. Тези пък, които касаят обектите, касаят комбинирането на обекти с цел добавянето на нова функционалност по време на изпълнението на програмата. Все повече уеб сайтове и приложения се изграждат посредством употреба на система за управление на съдържанието (CMS – Content Management System). Неведнъж ми се е налагало да имплементирам различни по мащаб подобни системи с цел реализирането на гъвкаво и лесно за управление и поддръжка приложение. Всеки който е работил или правил подобна система може да ви каже, че един от ключовете към успешното реализиране на такова решение е разглеждането на уеб страницата като съвкупност от визуални модули. Важно е доброто модулиране на компонентите, така че те да могат да бъдат използвани гъвкаво, да бъдат размествани и вграждани един в друг свободно и да са в някаква степен универсални. Composite дизайн патернът е един от ключовете към създаването на подобен вид визуални модули, които, подобно на части от детски конструктор, да съставят по-сложни съставни компоненти, запазвайки еднакви свойства и поведение. (Gamma, p.163) Composite често е естествен избор при съставянето на йерархия от обекти от някакъв рекурсивен характер, каквито примерно са елементите в едно XML/xHTML дърво. Всеки елемент от дървото или е краен, или е съставен от други крайни или съставни елементи. Рекурсията е залегнала в природата на този патерн и това го прави доста практичен за много приложения. Освен визуализацията на уеб
  • 12. страници чрез модули и съставянето на рекурсивни дърва при парсването на mark-up езици, Composite е и редовен избор за правене на рекурсивни менюта и други подобни атрибути на един уеб сайт. Тъй като доста често информацията да тези елементи идва от записи в база данни, които изграждат въпросните йерархии, Composite структурата се прилага успешно в изграждането на рекурсивен Active record (патерн, който ще бъде споменат по-напред в изложението), така че когато на един обект-представител на йерархията, обикновено корена на дървото, се извика методът за зареждане или рендиране, той свършва своята лична част и после рекурсивно извиква същият метод върху своите съставни от същия вид, ако притежава такива. Пример за подобен вид рекурсивна употреба на Composite е ползването му за описване, изграждане и визуализиране на едно рекурсивно меню с неизвестна дълбочина и степен на разклонение. Много често в уеб приложенията имаме именно подобен сценарий за употреба на меню, което се генерира динамично, на база записи в база от данни, и има рекурсивна структура без ограничения в броя на нивата. Рендирането на подобно меню се подразбира че трябва да е продукт на рекурсивна функция, както и обхождането му с цел намиране на даден елемент, както и почти всички действия върху него (зареждане, изтриване и т.н.). Ако си представим, че всеки елемент от менюто е наследник на даден MenuComposite клас, то освен собственото си съдържание като ред в менюто (заглавие и хипервръзка), той може да съдържа подменю с още N други елементи, всеки от тях сходен на описания. Съгласно горната схема на Composite, ако елементът от менюто няма подменю с елементи , то той се явява крайно листо (MenuLeaf). В противен случай той е MenuComposite. Ето примерна структура на класовете, изграждащи структурата на менюто:
  • 13. Целта е абстрактният клас MenuElement да е достатъчно общ, така че да може да представя както простите елементи, така и съставните. Всички елементи в йерархията споделят метода render(), който генерира чрез даден HTML шаблон техния краен HTML код. Рекурсивното изпълнение на този метод по йерархията представлява едно обхождане на дървото по ширина, в следствие на което съвкупността от генерирания HTML код от всеки елемент формира крайния HTML на менюто. Децата на MenuComposite класа са наследници на MenuElement, за да може към тях да се обръщат със същия набор от методи, като към другите елементи, независимо от характера им. Още по-голямата гъвкавост и сила на Composite идва от факта, че не е нужно въпросните елементи които се генерират да са от един и същи тип за да работи системата. Както е показано в горния пример, сред листовите елементи може да има някакви декоративни разделителни линии или заглавия на менюто или други подобни, които също са наследници на абстрактния MenuElement. Друг популярен и удобен създаващ дизайн патерн е Proxy. Proxy е патерн, който има толкова много приложения, че е трудно да бъдат изброени, но придържайки се предимно към уеб и уеб 2.0 приложенията, ще се опитам да дам нагледни примери за неговата универсалност и широко приложение. Много програмисти са използвали този патерн при реализацията на приложения с богати потребителски интерфейси, без дори да подозират за това. Едно от типичните приложения на прокси патерна е да действа като заместител/представител на дадени обекти, поради невъзможност или нежелание по някаква причина (сигурност, ефективност, скорост) да се работи с оригинала.
  • 14. Почвайки от ситуацията с невъзможността за работа с оригинала, Proxy често се използва в хетерогенни програмни среди, каквито са уеб 2.0 приложенията, където се използват повече от един програмни езици, кодът на които се налага да комуникира успешно за да се реализира пълната функционалност. Такъв е сценарият, в който даден богат потребителски интерфейс зарежда еднократно, обикновено при първоначалното си зареждане в браузъра на клиента, някакъв голям обект или масив/хеш от обекти от сървъра (обикновено сериализиран в JSON или XML) и го инстанцира като нов локален обект от съответния език от страна на клиента, обикновено JavaScript/ECMAScript Този обект се пази в паметта на браузъра и се използва многократно, с цел това да повиши скоростта при последващи операции по четене от него. Ясно е че рядко може конвертираният обект в JavaScript да притежава 100% от свойствата и методите на оригиналния обект, съставен от страна на сървъра на друг програмен език, а и няма нужда от това. В случая, Proxy-то от страна на клиента е заместител на оригиналния обект и обикновено се съставя така, че да има само свойствата (много рядко методи), нужни за прочитането и представянето му в клиентския интерфейс. Трябва да се отбележи, че демонстрираният по-горе начин на употреба на Proxy обекти служи по два начина: първо - като заместител на оригиналния обект, поради невъзможност да се борави с него директно, и второ - като филтър, който се прилага върху свойствата и методите на оригинала, така че да останат само желаните/нужните такива. Подобен на сценария с богатия потребителски интерфейс е използването на Proxy при осъществяването на друг вид комуникация, характерна за днешния етап от развитието на мрежата и услугите, които тя предлага. При осъществяване на комуникация чрез SOAP или подобен протокол, независимо дали става въпрос за отдалечено извикване на метод/процедура или за пренос на друг вид информация, често описателната сила на XML се използва за да може отсрещната страна на връзката да може да изгради от сериализираното съдържание на съобщението сто процентов заместител на оригинално изпратения обект. Не на последно място, Proxy може да има приложение при нарочното имитиране на дадени обекти от други “фалшиви“ такива. Този подход е особено популярен при unit-тестването и е известен като mocking, като обектите заместители – Proxys – се наричат mock objects. Техниката се използва когато трябва да се тества функционалността на даден метод, който приема като параметри и/или връща като резултат някакви по-сложни обекти или зависи по някакъв друг начин от такива. Тъй като фокусът на тестването е върху кода, който обработва информацията, а не върху самата информация или околна среда, често е препоръчително въпросните обекти да бъдат заменени от „фалшификати“, които обаче да споделят тези техни свойства и методи, които се използват от обработващия код, цел на тестването. (Hunt, Ch.6) Съществуват и структурни дизайн патерни, имащи цели сходни с тези на създаващите патерни, касаещи създаването на нови обекти от даден тип и по-конкретно специализацията на тези обекти. Такива патерни са Bridge и Decorator. И двата следващ обща цел да предотвратят създаването на архитектура от класове, която в един момент става прекалено тежка и трудна за поддържане. Такива са случаите, когато от даден първоначален абстрактен клас почнат да се родят множество унаследяващи го
  • 15. такива, всеки от които с неговата си по-тясна специалност. Проблемът идва когато критериите за наследяване поради специализация станат прекалено много и особено когато са на различни нива в йерархията. При Bridge целта е да се раздели абстракцията от имплементацията, тоест абстрактният клас, който дефинира даден обект, да може да продължава да се разслоява (да бъде унаследяван и децата му да го специализират в различни посоки), но в същото време имплементацията (реалният обект) да може също да приема различни форми. Примерът, който е илюстриран на следващите графики представлява онагледена ситуация, при която даден базов клас трябва да може да бъде разделен на няколко подвида, но обект от всеки подвид да може да бъде допълнително специализиран по определени свойства. В случая става въпрос за решаване на проблем с изчисляването на цената на дадена напитка, плюс съответните възможни добавки към нея, в кафене. Примерът и илюстрациите към него са взети от книгата ”Head First Design Patterns” на издателство O'Reilly (Freeman, Ch.3). Ето оригиналният базов абстрактен клас за напитка, плюс неговите конкретни имплементации – подкласове. Основният метод който ни интересува е методът cost(), който връща цената на съответната напитка. (Freeman, p.80) На следващата графика пък е илюстрирано какво се случва с тази засега спретната йерархия, когато се наложи да се разслои според други свойства на обекта, описван от базовия клас. В случая,
  • 16. всяка от четирите напитки може да бъде предложена с добавка: мляко, соево мляко, мока или бита сметана. Комбинациите стават страшно много, поради възможните пермутации на добавките, умножени по броя на напитките. Само част от възможните класове, които трябва да бъдат написани за подобен вид специализация са представени на долната графика. (Freeman, p.81) В подобна ситуация, ако не се приложи решение от рода на Bridge се получават като краен ефект 1 + m * n на брой класове, където 1 е оригиналният абстрактен клас, m е броят на класовете унаследяващи го с цел определена специализация, а n е броят на класовете, унаследяващи го с цел конкретна имплементация. Ако случаят е като горният, в който имплементациите n могат да се комбинират, тогава формулата нараства до 1 + m * n!
  • 17. Благодарение на Bridge, този брой може да бъде сведен до 1 + n + m. Любителите на алгоритмите, а и не само те, веднага ще забележат огромната разлика в разряда. Количеството спестена работа по разслояване и още по-важното – главоболието по поддръжка на подобна йерархия е огромно. (Gamma, p.151) Bridge архитектурата обаче има определени недостатъци – чрез нея няма да можем да постигнем всички възможности, илюстрирани на горните графики с многото комбинации за напитките. Там може да комбинираме само конкретна имплементация (например напитка с мляко) с конкретна специализация на базовия клас (например напитката кафе), като заменим вътрешното свойство в конкретния дъщерен клас с обект от въпросната имплементация. Тоест, възможно е да имаме само кафе или кафе с мляко, но не и кафе с мляко и бита сметана. В някои случаи това ограничение е допустимо, в други не. За другите случаи има съществуват съответните други решения. Декораторът пък, още познат и като Wrapper (опаковка) също служи за решаване на проблем, при който функционалността на даден обект трябва да може да се разширява, при това по време на изпълнение на програмата, но без да е нужно да се създават n на брой допълнителни класове унаследяващи базовия. При него различната имплементация на обект от общия базов клас се постига не чрез наследяване на базовата имплементация, както при Bridge, чрез създаване на съвсем нова класова йерархия, коренът на която има като свойство обект от базовия клас. Добавянето на функционалността не става чрез наследяване от базовия клас на този обект, а чрез „обвиването“ на въпросния обект във функции, които изменят базовата функционалност. Така вече се запазва не само възможността за промяна по време на изпълнение на програмата, но и въпросната функционалност може да се обвие с неограничен брой допълнителни такива чрез „обличане“.
  • 18. Ето как изглежда принципната класова диаграма на декораторът: (Gamma, p.175) Декораторът би ни позволил да получим всичката желана свобода за получаването на желания брой комбинации, без да е нужно да създаваме всичките онези класове. Нужно е просто да имаме един базисен декоратор, съдържащ като свойство обект от даден тип напитка, и да създадем четири наследяващи го декоратора за всеки тип добавка – мляко, соя, мока и бита сметана: (Freeman, p.92)
  • 19. Трикът се дължи на възможността на декорираните обекти да се съдържат един в друг и по този начин да се комбинират свойствата на декораторите. Това многократно обвиване на на обектите един в друг е възможно благодарение на общия им родител – абстрактния клас Beverage. Всеки дъщерен клас притежава базисния метод cost(), но декориращите класове добавят към оригиналната функционалност техните специфични промени, така че да „декорират“ обекта, според нуждата. Ето примерна илюстрация на това как цената на напитката се променя, според това как е декорирана тя: (Freeman, p.90) Поведенчески дизайн патерни: Поведенческите патерни имат за цел да идентифицират общи, често срещани начини/патерни на комуникация между обектите и да ги реализират, за да се постигне по-гъвкава комуникация. Те касаят алгоритмите и разпределянето на отговорностите между обектите. Целта е да се намали степента на сложност в комуникацията между обектите и по този начин да се постигне и по-малка степен на обвързаност между тях. Принципът на loose coupling гласи че колкото по-малко знаят един за друг обектите и класовете, толкова по-добре, защото по този начин не разчитат експлицитно един на друг и промените в тях са по-лесни за прилагане. Observer е един от най-популярните и често прилагани поведенчески патерни още от зората на обектно-ориентираното програмиране. Той е залегнал и в сърцевината на класическата MVC архитектура (framework) на Smalltalk. Също познат като Publish-Subscribe, този патерн осъществява комуникацията
  • 20. между обектите в не една и две архитектури. На практика, почти всяка библиотека за изграждане на графична среда (пример: Java Swing, Qt, Delphi) използва вариации на този патерн за да имплементира комуникацията между отделните си модули и обновяването на съдържанието на прозорците. Основните играчи в схемата са Observer обектите (наблюдатели) и Subject обектите (субекти). Обикновено субектите са обектите, съдържащи информацията която трябва да бъде представена, а наблюдателите са отговорни за самото представяне, затова те зорко следят за промени в изходната информация, които трябва да бъдат отразени. Ето примерна схема на ролите в едно графично приложение за електронни таблици: (Gamma, p.293) Тази архитектура се реализира софтуерно посредством абстрактен клас Observer, абстрактен клас Subject и неограничен брой унаследяващи ги конкретни имплементации. Основната функционалност, залегнала в базисните класове, е следната: субектът трябва да има методи за прикачане и откачане на наблюдатели – Attach( Observer) и Detach( Observer ), както и метод за известяване – Notify() - на промяна във вътрешното му състояние, която е от интерес за наблюдателите. Закачайки и откачайки наблюдатели, субектът поддържа вътрешен списък от такива, които изцикля при извикване на метода за известяване, извиквайки методът за обновяване Update() на всеки един от наблюдателите. Този метод е единствен за абстрактния клас Observer и също е абстрактен, тъй като конкретно какво се случва при промяна зависи от конкретната имплементация на обекта-наблюдател. Класовете които унаследяват Subject трябва да имат методи за присвояване и връщане на състоянието, което е от интерес на обектите наблюдатели. Посредством тези методи, обектите от класовете които наследяват Observer осъществяват функционалността на наследения абстрактен метод Update(); Хубавото на тази схема е че за субекта не е нужно да знае нищо за наблюдателите, които го следят. Той просто е длъжен при промяна да извика
  • 21. наследения си метод Notify(), който ще извърти абонираните наблюдатели, уведомявайки ги за промяната чрез извикване на униформен метод. Ето класовата диаграма на описаната горе конфигурация: (Gamma, p.293) Тъй като съвременните тенденции в уеб приложенията ги приближават все по-близко до локалните приложения от гледна точна на потребителски интерфейс и усещане, няма нищо по-естествено от това патерн като Observer, който се справя блестящо със задълженията си да регулира предаването на съобщения в локалните графични приложения, да намери своето място в уеб приложенията. Той е изключително удобен за осъществяването на комуникацията между уеб сървъра и браузъра с цел опресняване на съдържанието, както и за комуникацията и синхронизацията между различни обекти, находящи се в богатия потребителски интерфейс (AjaxPatterns, 1). Ще представя пример, в който се демонстрира и от двата вида приложение: Представете си богат потребителски интерфейс, който има за цел да представя на потребителя визуализация на някакви актуални данни, които се четат от уеб сървъра и претендират за постоянна актуалност, примерно графика на фиксинга на дадена валута или цена на акция. В идеалния случай, ако това беше локално приложение, би било най-ефективно графиката на фиксинга да се променя при настъпване на промяна в цената въпросната валута или акция. За съжаление, при уеб приложенията сървъра няма инструмент и или способ, чрез който активно да се свърже с браузъра на клиента и да го извести за промяната. Поради това ограничение подобни приложения поддържат отворена връзка от браузъра към сървъра, по която регулярно да пускат заявки, чрез които да си опресняват данните. На подобен принцип работят множество онлайн чат приложения или динамични табла за съобщения. Ако въпросните данни са нещо по-сложно от обикновена числова стойност (би свършила работа за фиксинга), то те обикновено се връщат от сървъра в сериализиран вид като JSON или XML, както при RSS feeds.
  • 22. Когато върнатите данни представляват обект, обновен и изпратен от уеб сървъра, тогава често е добър подход да се използва Proxy патерна, за да се направи огледален обект в скриптовия език от страна на клиента. Този обект може да се реализира като наследник на Subject класа, така че да има методи Attach( observer ), Detach( observer ) и Notify(), чрез които да може да приема, известява и премахва Observer обекти, които се интересуват от промените в неговото състояние. От друга страна, може да има и други субекти в JavaScript, от които зависи визуализацията на даден компонент, например входни елементи във форма, указващи други параметри на споменатата графика на фиксинга, от рода на скала на деленията, мерни единици, период и др. Те също би трябвало да се реализират като субекти, за които са абонирани наблюдателите, които „рисуват“ графиката. Това е пример за browser-to-browser комуникация с използване на Observer патерна. При класическата реализация на Observer, каквато е демонстрирана в книгата на четиримата, известяването на наблюдателите от страна на наблюдавания субект става чрез неговия метод Notify(), който в същност се налага да извика еднаквият метод Update() на всеки от регистриралите се за него наблюдатели. Този подход изисква минимално знание за структурата на наблюдаващите от страна на наблюдавания, но все пак при JavaScript може да се подходи още по-хитро и вместо да абонираме наблюдателите за субекта и той експлицитно да вика Update() в Notify() метода си, може просто да се възползваме от вградената в DOM и JavaScript функционалност за настъпване, следене и обработка на събития (events). Още от времето на HTML 3 в по-напредналите браузъри съществува функционалност за следене на определени DOM събития и извикването на скриптов код при настъпването им. В HTML DOM подобни събития са onload, onunload, onclick, onfocus, onblur, onchange, onmouseover, onmouseout и др. (W3Schools). Днес повечето, ако не всички, популярни библиотеки и frameworks за разширение на вградената JavaScript функционалност (PrototypeJS, JQuery, Dojo) имат инструменти не само за по- интелигентен начин за следене и улавяне на вградените DOM събития, отколкото използването на гореспоменатите HTML тагове, но и предлагат на програмиста възможността да дефинира свои събития, които после неговите обекти да предизвикват, а други методи и обекти да улавят. Тези собствени събития носят семантиката на приложението. Създаването и улавянето на специализирани събития позволява на програмиста да построи архитектура на потребителския интерфейс, чрез Observer патерна, в която субектите просто трябва при нужда да създадат (или жаргонно да „изстрелят“) съответното събитие, за което пък нужните наблюдатели са абонирани. Абонирането може да става с помощта на специализиран клас – диспечер. По този начин нашият прокси обект от страна на клиента бива обновяван чрез периодични обръщения към уеб сървъра и при настъпила промяна спрямо предишното му състояние да изстрелва съответното събитие, което ще причини абонираните за него наблюдатели да се само-обновят. Както при стандартните обектно-ориентирани езици за програмиране, които стоят от страната на сървъра, така и при скриптовите обектно-ориентирани езици от страна на клиента, какъвто е JavaScript, запазването на капсулацията на данните (един от основните принципи на ООП) е нещо желано, което гарантира висока гъвкавост и лесна поддръжка на приложението. И от двете страни прилагането на
  • 23. Observer патерна спомага за това нивото на капсулация да не се нарушава от нуждата обектите, които трябва да комуникират, да е нужно да знаят един за друг и да си викат експлицитно методите един другиму. Друг особено полезен и популярен стар патерн, който спомага както за запазването на капсулацията, така и за изграждането на гъвкави и удобни приложения с потребителски интерфейси е Memento. Както името му подсказва, той е свързан със запаметяване на определено състояние на даден обект във времето, предаването на това състояние на друг обект, който се интересува то да бъде запазено или възвърнато и самото действие на презареждане на първия обект в запомненото състояние (своеобразно пътуване назад във времето). (Gamma. p.283) Най-нагледният пример за употреба на мементо е приложение с потребителски интерфейс, поддържащо обратимост и възвращаемост на действията на потребителя тип undo / redo. На повечето напреднали (че и не дотам напреднали) потребители ни е трудно да си представим използването дори на прост текстов редактор без употребата на вълшебната Ctrl + Z комбинация, да не говорим за по-сложни приложения от рода на среди за разработка на програми или графични приложения. Следвайки предначертаната вече пътека с аналогията между локалните и уеб-базираните приложения, обръщаме внимание на тенденцията за постепенното премахване на границата между тях от гледна точка на потребителя. Може смело да заявим че едно уважаващо себе си уеб приложение с достатъчно богат потребителски интерфейс трябва да може да предложи на потребителя безпроблемна и лесна обратимост на действията, подобно на тази с която е свикнал в локалните приложения. Това изискване е още повече в сила когато става въпрос за някакъв вид редактиране на съдържание, като например в един уеб-базиран HTML редактор или в приложение за управление на съдържанието на сайта, където потребителят пренарежда и редактира дадени визуални модули. Ето как изглежда общата клас диаграма на мементо патерна: (Gamma. p.283) В общи линии обектите които са обект на запазване на състоянието наследяват класа Originator, който трябва задължително да има два публични метода – CreateMemento() и SetMemento( Memento ), съответно за създаване на нова мементо репрезентация и за възвръщането на състоянието си от
  • 24. подадена такава. Самият създаден мементо обект унаследява клас Memento, дефиниращ също два публични метода: SetState() и GetState(). Те са огледални на тези в оригиналния обект, като биват използвани съответно при създаването на ново мементо или при възстановяването на състоянието на оригиналния обект от вече създаден мементо обект. Този обект също така има свойство state, описващо именно запазеното статукво на originator-a в момента на създаването на мементо обекта. Трябва да се обърне внимание, че този state далеч не е нужно и не е желателно да представлява всички свойства на originator, а само тези които представляват интерес за трети обекти, които ще манипулират originator-a. В това се състои свойството на мементо да запазва капсулацията на данните на оригиналните обекти, разкривайки само това което е нужно (или в някои случаи нищо). Самите мементо обекти са пасивни – не предизвикват никакви действия от само себе си. Те само служат за да бъдат създавани от оригиналния обект, предавани на този който го интересува (обикновено този който ще променя оригиналния обект или някой специализиран обект-пазител на състоянията) и връщани обратно от трети страни на собственика, за да се възстанови от тях. Тази поредица от действия е отразена на следващата диаграма: (Gamma. p.283) Нека видим как мементо помага за имплементацията на undo/redo функционалност в едно уеб приложение от рода на система за управление на съдържанието. Представете си че имаме интерактивен редактор на визуалните модули на сайта, в който чрез drag & drop аранжираме местоположенията на отделните визуални модули, като тази конфигурация може да е различна за всяка една страница от сайта. Приемаме че архитектурата на приложението е такава, че всичката интеракция между потребителя и приложението става от клиентска страна с цел по-голяма динамика, без правене на връзка към уеб сървъра преди финалното потвърждаване на промените. Също така приемаме че имаме един обект (най-вероятно в JavaScript), който служи за структурно описание на редактираната от нас страница. Освен информацията за това какви модули има визуализирани на тази страница, той пази и данни от
  • 25. рода на съдържанието на страницата, нейното име и заглавие, HTML мета данни, данни за това кога е създадена и последно променяна тази страница и др. Този обект служи за първоначалното рендиране на редактора, като стартовото състояние отразява това на съответните данни записани от страна на уеб сървъра. След зареждането почва редактирането, което, както казахме, се извършва само от страна на клиента преди той да извърши действие за окончателно потвърждаване на промените. Ето примерна структура на класовете на undo/redo системата: Целта е да изградим undo/redo система, която да позволява на потребителя да отменя N на брой стъпки назад направените от него действия и в случай че се откаже от отмяната да приложи същите направени промени отново. За целта се изисква при всяка промяна да се извиква методът registerChange() на UndoRedoSys, който от своя страна ще изиска от Page обекта да произведе мементо (ще извика CreateMemento()), което мементо ще бъде съхранено в стека с промените на UndoRedoSys обекта, като се обновява и индекс на това кой елемент от този стек отразява моментното състояние (в нормално състояние, ако не е викано undo, последният + 1). След като тези действия са извършени промените могат да бъдат приложени „на живо“ върху Page обекта. От гледна точна на хронологията на потребителските действия, това се случва когато потребителя извлачи и пусне даден визуален модул в ново местоположение, преди самото пускане на модула да „залепне“. В последствие undo или redo функционалността се изпълнява при извикване на съответното събитие (примерно натискане на бутон undo в интерфейса или на даден клавиш от клавиатурата, като класическата Ctrl+Z комбинация). Тогава на база индекса на промените се взима предишният елемент от стека с промените, който представлява мементо обект, и този мементо обект се предава на метода SetMemento( memento ) на Page обекта, както в диаграмата по-горе. Page обекта обновява нужните свойства от записаните в мементо обекта, след което може да бъде извикано рендирането на страницата от променения Page обект. Redo се осъществява по аналогичен начин, само че чрез предвижване на индекса на промените напред по стека, в случай че това е възможно. Някой колега може би ще се запита защо просто не сериализираме обекта Page, описващ състоянието на редактора в JSON, като пазим сериализираните версии в подходящ за това хеш или масив във видимия обхват на адресното пространство в JavaScript или като свойство на UndoRedoSys.
  • 26. Да, това определено ще свърши работа за запазването на състоянието и предаването на данните, но по никакъв начин не спомага за другия положителен аспект от употребата на мементо, а именно запазването на капсулацията. На UndoRedoSys обекта не му трябва да знае и да има всички свойства на Page обекта. От гледна точка на ООП това има голяма стойност, дори при езици като JavaScript, в които няма частни променливи на обектите. Именно поради факта, че няма подобно ограничение върху свойствата на обектите и ценен механизмът на мементо, позволяващ в обекта който съхранява състоянието да бъдат записани само тези свойства, които са от значение за възвръщането на старото състояние, а не поголовно всички свойства на оригиналния обект. Не на последно място, това спомага и за намаляване на нужния обем от памет за съхранение на запомнените състояния. Ако става въпрос за пазене на две- три стъпки назад на състоянието на някакъв прост редактор с малък обем на редактираното съдържание това не би имало особено значение, но при 200-300 стъпки назад, което не е много за някои приложения, и голям обем данни в Page обекта, всеки спестен байт е от значение за скоростта на браузъра на клиента. Други видове софтуерни дизайн патерни Освен програмните дизайн патерни има и други видове софтуерни дизайн патерни: архитектурни, интерактивни, както и такива които са специфични за дадена категория софтуер. Архитектурните патерни са се сдобили с може би най-голяма популярност, поради големия мащаб на техния ефект. Те описват фундаменталната структурна схема на организацията на дадено софтуерно приложение. При тези патерни обаче абстракцията е на толкова високо ниво, че за разлика от програмните патерни, между две реализации на един и същ архитектурен патерн може да има огромни разлики. Въпреки това, те се използват често за да дефинират основният характер на дадено софтуерно приложение, дори като фактор за категоризация на софтуера в основни групи. Сред популярните архитектурни патерни са разслоените приложения (Layers), N-tier, Peer-to-Peer, SOA (Service-oriented Architecture) и всемогъщия MVC (Model-View-Controller), споменат неведнъж и в този документ. (Wiki, 4). Описанието на архитектурните патерни, поради техния мащаб, е извън рамките и целите на настоящата работа, но въпреки това се чувствам длъжен да отдам, макар и малко в телеграфен стил, нужното уважение на MVC патерна. MVC възниква като архитектура десетина години преди написването на книгата на „четиримата“ и поради своя архитектурен характер не е категоризиран като патерн в нея, въпреки че е споменат неведнъж. Неговият произход е свързан с възникването на обектно-ориентираните езици, по-специално с първия им представител – Smalltalk. MVC е част от стандартна библиотека за създаване на приложения с потребителски интерфейс на Smalltalk. Оригиналната му имплементация е описана в труда на Тригве Рийнскауг (Trygve Reenskaug) “Applications Programming in Smalltalk-80: How to use Model–View–Controller“ през 1979г., който бързо става популярен в професионалните среди. (Wiki, 5) Основната идея на MVC е отделянето на презентацията на данните от бизнес модела, който ги управлява, като остава един трети компонент – контролерът – който до голяма степен менажира тази връзка между потребителя и бизнес обектите.