Flyweight design pattern

Идеята на този design pattern е да даде начин ефективно откъм памет да нямаме огромен брой близки обекти, на които само част от информацията им е специфичнасто казано, трябва ти обект, подобен на някой вече съществуващ но с някои разлики, вместо да създаваш нов – вземаш съществуващият и му задаваш специфичните за случая неща и си го използваш.

Например, ако си представим уеб страница с хиляди картинки (<img> тагове), които обаче са със един и същ файл (src атрибута) или са няколко с един си същ src, и само други техни свойства (width, height …) се различават. Не е ефективно за всеки да имаме по един обект, при условие, че можем да имаме само по един на база src атрибут (файла), а останалите да са само същият този обект но с различни пропъртита.

Или текстов редактор с много символни знака, които можем да избираме, и всеки е отделен обект но се различават само по самият символ.

Един начин би бил естествено да създаваме нов обект всеки път когато логиката го изисква. Но какво ще стане с паметта, която приложението използва ако например имаме документ с стотици хиляди символа и всеки символ е един обект?

Първо нека логически да разделим общите от частните свойства на тези много, близки обекти, тоест да видим по какво се повтарят.

Ще наричаме общите свойства intrinsic (или invariant) – вродени (непроменящи се), а специфичните – extrinsic (или variant) – тоест привнесени отвън, динамични.

В нашите примери intrinsic ще е src атрибутът на img обектите, защото URL-ът/файлът ще използваме като уникален ключ в data структура, от която ще го вземаме наготово за да го „напаснем“ според случая, вместо да създаваме нов.
А extrinsic ще са например отделните свойства на img обекта, които ще подаваме на вече съществуващият, вместо да създаваме нов.

В примера с текстовият редактор, ще имаме по един обект за всеки символ, intrinsic ще е кодът му, а extrinsic ще са такива като ред, колона, шрифт, цвят и т.н…, неща свързани с конкретната позиция на символа в текста, и които ще подаваме отвън.

Ще имаме един абсклас Flyweight, в който ще са intrinsic свойствата, и два наследяващи го класа – ConcreteFlyweight и UnsharedFlyweight.

Клиентите използват инстанция на ConcreteFlyweight и само подават extrinsic свойствата динамично (run time) според конкретната логика, а не ги използват за да създават нови обекти.
Тоест, ще имаме един обект, просто ще го променяме според нуждите, вместо да създаваме нови.

Друг добър пример би бил ако например искаме да имаме обект за всеки запис от база данни. Ако са милиони? Милиони обекти от дадения клас?
Или просто един обект, на който променяме стойностите на пропъртитата, които кореспондират с полетата на резултата на SQL заявката.

Няма да минем и без „factory“, което ще следи дали вече има такъв създаден или не, и ако не – създава го и го добавя по даден уникален ключ в някаква data структура, която ще ни е container от обекти.
To ensure that Flyweight objects are shared properly, clients must obtain flyweights solely from the flyweight factory (getFlyweight(key)) that maintains a pool of shared Flyweight objects. This greatly reduces the number of physically created objects.

В това отношение Flyweight design pattern силно прилича на Singleton design pattern.

Като всяка design pattern и Flyweight трябва да се използва разумно, в смисъл такъв, в конкретни случаи. Прилагайте Flyweight само когато всички тези условия са налице:

  • приложението използва огромен брой близки като стуктура обекти, на които само някои от пропъртитата се променят, от там и storage cost (паметта например) скача силно;
  • Most object state can be made extrinsic;
  • имаме много обекти, които са много близки и имат свойства и могат да бъдат заменени с един, съдържащ общите такива, а различните – да му се подават отвън;
  • The application doesn’t depend on object identity. Since flyweight objects may be shared, identity tests will return true for conceptually distinct objects.

Литература:

https://refactoring.guru/design-patterns/flyweight

Bridge design pattern

Да си представим проста клас-структура от например абсклас и неговите наследници, която да е йерархична и да пресъздава например продуктите в хипермаркет за електроника.

Имаме например клас ProductAbstract, който ще има няколко подкласа, например: BlackElectronics, Kitchen, PC, Smartphones…

Под BlackElectronics можем да имаме например: TV, HiFi…

Под TV – например телевизори Panasonic, Sony… и т.н…

Aко например искаме за всеки продукт да имаме 3 цени в три валути, един начин е под всеки от горните класове да имаме по 3 класа за всяка от трите валути, напр: PanasonicBGN, PanasonicEUR, PanasonicUSD, SonyBGN, SonyEUR, SonyUSD…
(Знам, че примерът не е добър, не обръщайте внимание, нека схванем идеята.)

Веднага се вижда, че по този начин ще имаме Декартово произведение от класове, щом комбинираме всеки с всеки.
Отделно, ако ни трябва нова валута?
Пиши народе възродени още N на брой класове за телевизори, перални…

Няма ли да е по-добре да имаме отделно три класа например, за отделните валути – PriceBGN, PriceEUR и PriceUSD, и за да са заменяеми – да имплементират общ интерфейс, например PricesInterface?

И в първата структура (с продуктите) ще имаме едно protected property например $priceObj, където ще е текущият обект за валута, която ни трябва за да сметнем цената на продукта. Демек, той ще има например метод getCurrencyCourse()

Така, вместо за всеки клас Panasonic да имаме примерно 3 подкласа за трите валути, ще имаме само Panasonic, а от това кой от трите класа за валути имаме в $priceObj, имаме и валутният курс, който ни трябва да си сметнем цената.

Eто това е идеята на Bridge design pattern – да раздели отделните структури от класове, отделните абстракции отделно, за да могат да се развиват независимо и това да не предизвика експлозия от класове като Декартово произведение.

Демек, този design pattern е идеален в случаите когато дадена структура от абстрактни класове и подкласове може да стане твърде сложна, с много повторения на код, да експлоадира от класове, и не на последно място – малка промяна (нов абсметод в родителски клас – абсклас например) да наложи същата промяна и в подкласовете. Последното например, ще наруши Interface segregation principle.

Между другото, важно е да се знае, че двете абстрактни структури, за които говорим могат да са напълно независими една от друга.

Също и е важно да се знае, че този „мост“ между така да се каже „основната“ абсструктура (за продуктите) и така да се каже „помощната“ (за валутите)… може да имаме много такива „мостове“, и към други „помощни“ абсструктури“…

Например ако искаме да имаме два изгледа на страниците за всеки продукт – short (с малко инфо) и long (с пълната информация), можем да имаме още една абсструктура ViewInterface с два подкласа ShortView и LongView. И съответно, и за тази абсструктура да има едно protected property $view.

И не на последно място, примерът с цените на продуктите по-горе, е само за едно свойство на продукта, само за пример. По принцип така трябва да се направи за всички пропъртита на продуктите. Демек, в абсструктурата ProductAbstract трябва да остане само структурата на продуктите, демек, йерархичните зависимости кой под кого е и т.н…
Самите неща, специфични за отделните продукти, трябва да се инджектват (бриджват) от други абсструктури, подобно на цените.

Да разгледаме един друг пример

Имаме абсструктура BaseRepository, чиято идея е да може да съдържа подкласове за например студенти (StudentRepository) и т.н..

И съответно StudentRepository също ще имам например два подкласа SaveStudentInFile и SaveStudentInDB със своите съответни методи за например различни валидирания, проверки и т.н…, и също и методи за запазване на информацията във файлове или в база данни.

Вижда се веднага, че първо имаме повторение на методи и уникалната част е само тази за запазването на информацията.

Второ – при нужда от трети, четвърти… начин на запазване, това значи още подобни класове, и още повторение…

Не е ли по-елегантно:

1) да оставим в тази сегашна абсструктура само нещата, свързани само със студентите – проверки, валидации и т.н…

2) да създадем втора, паралелна абсструктура например StorageRepository, която ще има например два подкласа FileStorage и DBStorage, които освен специфичните си методи ще имат и един публичен например save().

3) обект от StorageRepository ще бъде инджекват в обект от Students, например през констуктора, и ще бъде използван за запазване на информацията за студентите, чрез извикване на метода save().

Eто как като разделихме двете абсструктури, можем да ги развиваме напълно независимо една от друга. Можем да имаме както не само студенти, но и да ги запазваме както си искаме, без да пишем по един клас за всеки вариант – SaveStudentInFile, SaveStudentInDB, SaveStudentInWhatever…, SaveProfessorInFile и SaveProfessorInDB…

Class diagram for Bridge design pattern in java

Литература:

Bridge design pattern in Java

Structural design patterns – comparison

Some patterns are mostly different in the intent, rather than the technicalities. They might be doing exactly the same thing, but the difference is that they are doing it for the different reason.“
Christopher Okhravi

Adapter vs. Proxy
„An adapter provides a different interface to the object it adapts.
In contrast, a proxy provides the same interface as its subject.“

„a proxy used for access protection might refuse to perform an operation that the subject will perform“
Design Patterns: Elements of Reusable Object-Oriented Software

Toест, Адаптер „напасва“ и пропуска винаги и няма никакъв access control, Прокси може и да не позволи изпълнение на дадената операция.

Decorator vs. Proxy
Although decorators can have similar implementations as proxies,
decorators have a different purpose. A decorator adds one or more responsibilities
to an object, whereas a proxy controls access to an object.“
Design Patterns: Elements of Reusable Object-Oriented Software

Decorator vs. Adapter
Адаптер задава нов интерфейс на адаптираният обект, като на практика прави нов обект, използвайки го.
Декоратор запазва интерфейсът на декорираният обект, като може да промени някои от нещата му, може да добави нови, но като цяло трябва да запаси интерфейса за да може както оригиналът, така и декорираният да са взаимозаменяеми (onion head).

Proxy design pattern

„Provide a surrogate or placeholder for another object to control access to it.“
Gang Of Four

Идеята на този design pattern е да „обвие“ обектът (Subject) с нещо като wrapper, който с помощта на дадена логика, да ограничи достъпа до обекта, с оглед например на:

1) дали изобщо имаме право да работим с обекта (security)

2) дали сега му е времето, демек, дали не може да изчакаме с инициализацията на даденият обект за да спестим ресурс (lazy loading)

3) дали вече обектът съществува и можем да го използваме наготово (Singleton)

4) дали да копираме обекта или не, в зависимост дали е бил променен (copy-on-write) или ако не – да пробутваме същият уж като копиран.

Тоест, за разлика от другите подобни design patterns, които:
1) добавят функционалност (Decorator)
2) или адаптират достъпа до даденият обект и отделно дават различен интерфейс към обекта (Adapter), при този design pattern ключовата дума е по-скоро „контрол на достъпа“.
Демек, имаме добавяне на логика но с намерение да контролираме достъпа, не да променяме или допълваме това, към което контролираме достъпа.

Контрол на достъпа, но не само от към гледна точка на сигурността. Това е само един от аспектите на този design pattern.

Може да имаме случаи, когато например да трябва предварително да проверим дали изобщо имаме обект, който да използваме.

Или дали обектът вече не съществува с оглед на това да не го създаваме пак (кеширане, подобно на Singleton design pattern).

Или например да приложим някаква валидация… и т.н…

В този смисъл на думата имаме три вида Proxy design pattern:
A remote proxy controls access to a remote object.
A virtual proxy controls access to a resource that is expensive to create.
A protection proxy controls access to a resource based on access rights.

Защо изобщо ни е нужно всичко това, този design pattern демек?

Не може ли да си напъхаме въпросната „логика на достъпа“ с самият Subject? Защо да я отделяме в друг клас, че през него да викаме Subject-а?

По принцип може но това може да „раздуе“ класа Subject и да нарушим Single Responsibility Principle.

Отделно, ако разсъждаваме по-абстрактно, често логиката, обстоятелствата, при които ни се налага да използваме даденият Subject може да не са толкова плътно свързани със самата логика на Subject-a (демек, „какво прави“ и „кога да го прави“).

Ако започнем да тъпчем логиката кога Subject-ът да се извика, в самият него, и тази логика е доста променлива например, всеки път ли трябва да го променямe (Subject-ът)?

Aко например искаме да станем в 5:00 сутринта, трябва ли да имаме тази способност в нас самите (еволюционно), след като можем да използваме будилник, който да ни събуди когато трябва? А ако утре трябва да станем в 5:30 например… хайде пак милиони години еволюция…

Как програмно се реализира Proxy design pattern?

С помощта на клас, който като аргумент в конструктора си например, приема обектът, който ще „проксираме“ (Subject) и в него (Proxy класът) ще имаме метод/методи, където ще е логиката, според която ще или не ще използваме проксираният обект. Или ще го използваме по определен начин и т.н…

Define a separate Proxy object that:
1. can be used as substitute for another object (Subject) and
2. implements additional functionality to control the access to this subject.
3. A proxy may hide information about the real object to the client.
4. A proxy may perform optimization like on demand loading.
5. A proxy may do additional house-keeping job like audit tasks.
6. Proxy design pattern is also known as surrogate design pattern.

This makes it possible to work through a Proxy object to perform additional functionality when accessing a subject. For example, to check the access rights of clients accessing a sensitive object.

To act as substitute for a subject, a proxy must implement the Subject interface. Clients can’t tell whether they work with a subject or its proxy.

Последното изречение е интересно. За да действа като Прокси, то (Проксито), както самият Subject (проксираният) трябва да имплементират един общ интерфейс. Защо? За да са взаимозаменяеми.

By implementing the same interface, the Proxy can be substituted for the RealSubject
anywhere it occurs. The RealSubject is the object that does the real work. It’s the object that the Proxy
represents and controls access to.

Литература:

https://en.wikipedia.org/wiki/Proxy_pattern

Facade design pattern

„Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.“
(Gang Of Four)

Идеята на тази design pattern е да служи като „фасада“ между потребителят и някоя друга, доста по-сложна структура, по начин, по който потребителят да няма нужда да знае много за въпросната структура, a да работи с нея през „фасадата“, която фасада вече трябва да знае много за въпросната структура. И по този начин – да опрости интерфейсът за достъп към цялата структура.

Нека си спомним за т.н. Law of Demeter – ако имаме т.н. „влакче“ от класове, които се наследяват например така: „А бива наследен от В, а пък B бива наследен от C…“, ако трябва A да използва методи на C, да не трябва да използва (да „минава през“) B, a да може директно да вика методи на C.

С Facade design pattern можем да спазим това правило.

Примерна ситуация би била, ако пишем или използваме някаква библиотека, която е доста сложна откъм OOP, можем ние сами да напишем една „фасада“, с която да улесним използващите библиотеката и да няма нужда те да познават OOP схемата и.

Какво представлява една „фасада“ от чисто програмна гледна точка?

Може да е например един „wrapper“ клас, чиито методи да викат методи на системата зад фасадата, които методи биха били нужни на потребителя.

И да не забравяме, че във Facade можем да викаме методи на НАПЪЛНО НЕЗАВИСИМИ ЕДИН ОТ ДРУГ КЛАСОВЕ, по този начин „сглобявайки“ си своя логика от различни „парчета“.
От този клас вземи този и този метод, от онзи клас вземи онзи и онзи метод…

Добър въпрос би бил дали Facade допуска добавяне на логика или само използва логиката на подсистемата, пред която стои.
Да, допуска.
A facade is free to add its own “smarts” in addition to making use of the subsystem.
Head First Desing Patterns

Use the Facade pattern when:

  1. you want to provide a simple interface to a complex subsystem. Subsystems
    often get more complex as they evolve. Демек, ако мислим в перспектива, че дадена система ще се усложнява…
  2. there are many dependencies between clients and the implementation classes of an abstraction. Introduce a facade to decouple the subsystem from clients and other subsystems, thereby promoting subsystem independence and portability. Демек, още един layer между клиента и системата.

Добра идея би била, Фасадата ни да бъде абстрактен клас и отделните и подкласове да са написани според конкретното използване на системата, „зад фасадата“.

Литература:

https://en.wikipedia.org/wiki/Facade_pattern

Adapter design pattern

Подобно на Decorator pattern, Adapter pattern също се използва когато имаме нужда на вече готов обект (т.е. runtime) и трябва да му променим поведението. С разликата, че Decorator добавя функционалност, а Adapter я променя и напасва.

Може и да добавя логика, но идеята, the intention е да напасне, не да разшири функционалността.

Както казахме, много Design patterns страшно си приличат и застъпват, but the intention, the reason for using them, is what makes the difference which one to choose, not the concrete implementation.

Тоест, Adapter design pattern трябва да се използва когато искаме от класът който ще адаптираме това което ни трябва, без да му добавяме още логика, но просто не можем да го използваме директно.

Например, малките адаптери за различните ел. контакти когато пътуваме в чужбина са точно пример за Adapter design pattern.

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.

Как най-просто става това?

Като например в Adapter класът инджектнем като property (HAS-A) обектът, който ще адаптираме (нека го наричаме Adaptee), и който (Adapter) ще използва неговите (на Adaptee) публични методи в своите методи, за да зададе друга логика, като използва и променя логиката от Аdaptee (инджектнатият) обект.

И на практика, т.н. Client ще може да използва методите на Adapteе, но не директно, а през Adapter. Демек, съответният метод на Adaptee, който е нужен на Client, той (Client) ще вика съответният всъщност на Adapter, а Adapter ще вика съответният на Adaptee като преди това ще извършва своята работа по „адаптирането“.

„The adapter design pattern allows otherwise incompatible classes to work together by converting the interface of one class into an interface expected by the clients.“

Toва ще рече, че за да приложим на практика този Design pattern, трябва да имаме пълно познание за интерфейсът на адаптирания клас (Adaptee) и също и на Client класът.

На пръв поглед излиза, че Adapter design pattern е нещо доста просто и програмно се състои само от един клас Adapter, който стои между Client и Adaptee и адаптира извикванията към методите на Adaptee. Един клас, толкоз. Сигурно затова някои казват, че този design pattern бил на практика по-скоро един wrapper.

По принцип това е така, най-просто казано идеята на Adapter design pattern е точно такава.

Но сложността идва от начините на реализация.

Дали класът Adapter ще е просто един standalone (напълно независим) клас (т.н. Object Adapter Pattern)?
Или ще наследява адаптираният (т.н. Class Adapter Pattern)?

При Object adapter pattern той (Adapter) съдържа в себе си като пропърти обект от aдаптираният клас и използвайки неговият интерфейс (публичните му методи) извикава съответните методи (на Adaptee). Toзи подход е възможен разбира се, само в случаите, когато Adaptee класът е instantiatable.
Както се разбира, тук имаме composition (HAS-A), не inheritance (IS-A).

При Class аdapter pattern Adapter използва функционалността на Adaptee не по горният начин, a като го наследява, inheritance (IS-A), a не composition(HAS-A).

Литература:

https://en.wikipedia.org/wiki/Adapter_pattern

Decorator design pattern

„Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.“
(Gang Of Four)

Идеята на този design pattern е доста проста – ако искаме да променим какво даден клас прави, да не променяме самият клас или да изполваме наследяване (тоест „compiletime“), а да променяме обектът/обектите му (тоест „runtime“).

Казах „променим“, но по-скоро имах предвид „да допълним“.

Kак?

Като „обвием“ оригиналният („вътрешният“) обект в друг обект („външен“), и когато искаме да извикаме даден метод на „вътрешния“, всъщност вместо това ще извикаме метод на „външният“, който ще извика метод на „вътрешният“.
Отговорът пак ще минава през „външният“ обект.

Отделно външният може да бъде опакован в друг външен и т.н… като лукова глава.

Отделно, може да имаме случаи, в които не искаме да слизаме надолу до Компонента, а направо Декораторът (или някой от Декораторите) да върнат резултат.

Ще наричаме най-вътрешният обект Component, а опаковащият/опаковащите го – Decorator(s).

Важно е да се знае, че всеки „опакован“ обект, трябва да бъде напълно заменяем със своят „оригинал“ (с Component-ът).
„The Decorator is of the same type as the Component“.
„Interface conformance. A decorator object’s interface must conform to the
interface of the component it decorates.“

Този design pattern много напомня на наследяване на класове, но за разлика от наследяването, което се задава предварително, тук можем да постигнем на практика същият ефект динамично (at runtime).
Отделно, ако например нямаме multiple inheritance както в PHP, тук можем да симулираме такова, като използваме в Декоратора различни други класове, за да „украсим“ Компонента.
Да не говорим, че с наследяване може да се наложи според конкретният случай да трябва да пишем отделен клас, което да доведе до „експлозия от класове“.

Да си спомним Single Responsibility Principle, който задава, че един клас трябва да отговаря колкото може по-тясно и конкретно за своята задача, а не да бъде „раздут“ и универсален.
С този design pattern можем динамично да „раздуваме“ обектът Компонент, като използваме други класове, без да нарушаваме този принцип.

Пример:

Ето един прост пример, когато Decorator pattern би бил полезен: имаме пицария, вместо за отделните видове пици да имаме по един отделен клас, можем да имаме един базов клас Pizza който ще е съвсем елементарен, и вече отделни класове Декоратори (имплементиращи общ интерфейс) като Моцарела, КуатроСтажиони и т.н…, на които им се подава обект от Pizza и те го „доокрасяват“.

Идеята е не да има отделен клас за всеки модел пица, а да има отделен клас само за отделните съставки, и отделните пици само да ги комбинират. Това би било много по-гъвкаво, защото какво ако например някоя от съставките поскъпнат? Трябва по всички класове за модели пици да минем и да отразим новата цена.

Другият вариант, който също не е удачен, е да имаме един абсклас Pizza, в който имаме пропъртита за всяка съставка с цената и, и по един метод, от рода на hasOlives(), hasTomato()… на който подаваш true/false и на тази база, цената на съставката се добавя към общата.

Първи недостатък – ако с времето започнем да предлагаме нови и нови съставки, трябва да добавяме за всяка нови и нови пропъртита и has… методи. Което нарушава Open/Close SOLID Principle.

Второ, ами ако клиентът поиска двойни маслини? Трябва hasOlives() например да има още един параметър, или да не подаваме true/false, а quantity и ако е 0 – все едно имаме false.

Трето, нарушаваме и Interface Segregation SOLID Principle, защото щом главният клас Pizza е абстрактен, и has… методите също, трябва във всичките детски класове да ги имплементираме всичките, независимо че може да ни трябват само някои от тях.
„A subclass should not be forced to implement parent methods that it does not use.“

Идеята на Decorator design pattern е да разпредели всички възможни „поведения“ или свойства ако щете, на даден клас, в отделни класове, така че да може като ги имаме отделно, да можем по-гъвкаво да ги комбинираме, вместо твърдо да ги задаваме в всеки клас.

The Decorator pattern has at least two key benefits and two liabilities:

  1. More flexibility than static inheritance. The Decorator pattern provides a more flexible way to add responsibilities to objects than can be had with inheritance. With decorators, responsibilities can be added and removed at run-time simply by attaching and detaching them. In contrast, inheritance requires creating a new class for each additional responsibility.
  2. Avoids feature-laden classes high up in the hierarchy. Decorator offers a pay-as-you-go approach to adding responsibilities. Instead of trying to support all foreseeable features in a complex, customizable class, you can define a simple class and add functionality incrementally with Decorator objects. Functionality can be composed from simple pieces. As a result, an application needn’t pay for
    features it doesn’t use.
  3. A decorator and its component aren’t identical. A decorator acts as a transparent enclosure. But from an object identity point of view, a decorated component is not identical to the component itself. Hence you shouldn’t rely on object identity when you use decorators.
  4. Lots of little objects. A design that uses Decorator often results in systems composed of lots of little objects that all look alike. The objects differ only in the way they are interconnected, not in their class or in the value of their variables. Although these systems are easy to customize by those who understand them, they can be hard to learn and debug.

Литература:

https://en.wikipedia.org/wiki/Decorator_pattern