Tell, don’t ask

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

Казваш „направи това“ и даденият обект вече сам решава дали и как да го направи.

Как точно програмно?
Няма значение, по принцип говоря. Може да е например, с проверяване на стойността на пропърти, с извикване на метод на даденият клас…
Но това проверяване на пропъртита (asking, „питането“ дали можеш да го свършиш) не трябва да става „отвън“, а вътре в класа, тоест – чрез негов метод.

Демек, разсъждавайте на пинципа, че всички пропъртита на даденият клас НЕ са public.

Защо да не са public?
Не че ще свърши светът ако са public, но по принцип, с оглед на data encapsulation, е по-добре данновата част на класа да НЕ е public.

Демек, не го питай първо „можеш ли да свършиш това“, като му използваш пропъртитата, защото това най-малкото значи, че въпросните пропъртита трябва да са public. Което значи, че намаляваш капсулацията, въпреки че можеш да го сториш и през „getters“…

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

Или например, да имаш даден огромен обект, в който например с IF-ове да са заложени различни функционалности, и ти да му викаш даден метод и чак тогава да проверяваш коя от всичките функционалности по-точно, да му извикаш.

Демек, можем да имаме огромен обект Животно, в който да имаме метод doSound() в който да проверяваме куче ли е, котка ли е, лъв ли е, кандидат-депутат в предизборна кампания ли е…. и тогава въпросното Животно да издава съответният си свой звук.

Демек:

if ($animal->type === 'dog') {
    echo 'bau bau';
} elseif ($animal->type === 'cat') {
    echo 'miau miau';
}
....

a по-скоро:

$animal->doSound();

като обектът $animal ще е обект от клас Kуче, клас Котка, клас Кандидат-депутат в предизборна кампания… и прочее Животни.

Демек, вместо първо да го питаме „що за животно си“, и тогава „аха, куче си – лай тогава“, „аха, котка си – мяукай тогава“ и т.н…. можем и да имаме отделни класове, наследяващи Животно, като например клас Куче, клас Котка и т.н… и да му казваме „doSound()“.

Искаш да нахраниш Животното, би било то от най-нисшето, до най-висшето?

ОК, но всяко животно яде определен тип храна. Тревопасните – растения, хищниците – месо…, програмистите – бира и хамбургери….

Също, преди да нахраниш даденото животно, то трябва да е гладно. Но за да определиш дали е гладно, зависи от самото животно. Всяко си има свои критерии. Ето още една „per-animal“ специфичност…. Която трябва да се отдели в класът, който е за самото животно. А не с IF-ове да се проверява кое животно е, че тогава дали е гладно….

Демек, викаш Kуче->eat() или Koтка->eat(), Програмист->eat()…, и вече в конкретният eat() метод на съответният клас, се решава дали даденото Куче, Котка, Програмист… е гладен, по свой си начин. Per animal…

Kaзваш му „яж“, и то да си решава дали да яде или не, и какво да яде.

Демек, не питай даденият обект „можеш ли да свършиш това“.
Кажи му да го свърши и нека той сам реши дали може и трябва, и иска да го свърши.

Цялата работа е да не се използват пропъртита отвън за разни проверки дали да свършим това или онова, защото по принцип е добре данновата част на класа (пропъртитата) да са private/protected, а да казваме на обекта на класа „свърши еди си какво“ и той да си знае сам.

Без да те интересува the internal state of the object. Не питай обекта за данните му, кажи му какво да прави. И нека той си знае сам работата.

Rather than asking an object for data and acting on that data, we should instead tell an object what to do

Литература:

https://martinfowler.com/bliki/TellDontAsk.html

Separation of concerns (SoC)

Според мен това е design принцип, много подобен на Inversion of Control, но в по-общ смисъл. Демек, IoC е по-конкретен само за OOP, SoC e по-общ…

Принцип при който чрез разделяне на функционалността на дадена програма по такъв начин, че отделните и компоненти (класове, пакети, библиотеки…) да са както максимално концентрирани само върху своите задачи и предназначения (single responsibility) и да имат максимално тясна специализация.

„In computer scienceseparation of concerns (SoC) is a design principle for separating a computer program into distinct sections such that each section addresses a separate concern.“

Въпросното „разделяне на задачи“ може да е на различен принцип. Това може да са задачи свързани само с визуалната част на дадената програма, както например HTML и CSS си разделят отговорностите, едното за структурата и семантиката, а другото – за чисто визуалната част на дадената страница. Затова в началото казахме, че „SoC e по-общ“.

Може да е чисто backend разделяне на програмата – един клас да отговаря за валидациите, друг за работа с базата данни…

Или още по-генерално как да се структурира програмата. MVC например е типичен пример за SoC – програмата е разделена на бизнес модел, презентационна част и т.н…

OOP encapsulation, granularity, dependency, flexibility, performance, evolution and reusability and other common concepts

Цитатите са от известната книга „Design Patterns: Elements of Reusable Object-Oriented Software“

Object types and interfaces

A type is a name used to denote a particular interface. We speak of an object as having the type „Window“ if it accepts all requests for the operations defined in the interface named „Window.“ An object may have many types, and widely different objects can share a type. Part of an object’s interface may be characterized by one type, and other parts by other types. Two objects of the same type need only share parts of their interfaces. Interfaces can contain other interfaces as subsets. We say that a type is a subtype of another if its interface contains the interface of its supertype. Often we speak of a subtype inheriting the interface of its supertype.

Interfaces are fundamental in object-oriented systems. Objects are known only through their interfaces. There is no way to know anything about an object or to ask it to do anything without going through its interface. An object’s interface says nothing about its implementation – different objects are free to implement requests differently. That means two objects having completely different implementations can have identical interfaces.

Видиш ли интерфейсът на даден клас, виждаш какво той може да прави, видиш ли самият клас (имплементация), виждаш как го прави.

Dynamic binding

When a request is sent to an object (calling some of its methods), the particular operation that’s performed, depends on both the request and the receiving object. Different objects that support identical requests may have different implementations of the operations that fulfill these requests (или казано иначе, имплементират по различен начин различни интерфейси). The run-time association of a request to an object and one of its operations is known as dynamic binding.

Dynamic binding means that issuing a request (calling а object method) doesn’t commit you to a particular implementation until run-time. Consequently, you can write programs that expect an object with a particular interface, knowing that any object that has the correct interface will accept the request.

Polymorphism

Dynamic binding lets you substitute objects that have identical interfaces for other, at run-time. This substitutability is known as polymorphism, and it’s a key concept in object-oriented systems.

Kакво е т.н. „интерфейс“?
Какво даден клас може да прави чрез методите си?
Да де, но не само. Интерфейс MagareInterface задължава имплементиращите го класове да могат да ревът и да хвърлят къчове.
Но идеята е интерфейс MagareInterface да задължи дадените методи на имплементиращи даденият интерфейс, да имат зададеният от интерфейсът сигнъчър, демек, да се знае какво се иска като типове параметри, както и като тип върнат резултат.

Classes that aren’t abstract are called concrete classes.

Object’s class vs. Object’s type

It’s important to understand the difference between an object’s class and its type. An object’s class defines how the object is implemented. The class defines the object’s internal state and the implementation of its operations. In contrast, an object’s type only refers to its interface – the set of requests to which it can respond. An object can have many types, and objects of different classes can have the same type.

Демек, класът на даден обект представлява самата му дефиниция. Това е все едно конституцията и законите на една държава, според които тя функционира.

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

„… and objects of different classes can have the same type.“ – много държави могат да имат обща външна политика най-общо казано, като например членство в едни и същи организации и т.н…

Разбира се, двете понятия за близко свързани помежду си.
Of course, there’s a close relationship between class and type. Because a class defines the operations an object can perform, it also defines the object’s type. When we say that an object is an instance of a class, we imply that the object supports the interface defined by the class.
Демек, понеже класът задава конституцията, то индиректно той задава и външната политика. И щом кажем, че обектът е от клас ЕдикойСи (демек има конституция ЕдикойСи), това по подразбиране значи, че той има и съответната външна политика.

Като пишеш един клас, винаги мислиш първо „какво трябва да може да прави“, и тогава, на тази база пишеш конкретната имплементация. Демек, класът задава конституцията но според това какъв трябва да е типът – външната политика. Явно това се има предвид с принципът „Program to an interface, not an implementation.“

Inheritance vs. Composition

The two most common techniques for reusing functionality in object-oriented systems are class inheritance and object composition. Subclassing is often referred to as white-box reuse. The term „white-box“ refers to visibility.
Демек, класът който наследяваш е напълно известен за теб, като негов наследник, затова явно се има предвид, че е „бяла кутия“. Наследяваш всичко, без разбира се private’s.

Object composition is an alternative to class inheritance. Here, new functionality is obtained by assembling or composing objects to get more complex functionality. This style of reuse is called black-box reuse, because no internal details of objects are visible. Objects appear only as „black boxes.“
За разлика наследяването, тук другите обекти биват „инджектнати“ като пропъртита. Които обекти могат и да не са ни известни като структура и т.н., дадени са ни да им използваме public пропъртитата и методите, демек знаем за тях само толкова, колкото трябва да знаем. Те за нас са като „черна кутия“.

Inheritance and composition each have their advantages and disadvantages. Class inheritance is defined statically at compile-time and is straightforward to use, since it’s supported directly by the programming language. Class inheritance also makes it easier to modify the implementation being reused.
Демек, така може по-лесно да се променя функционалността на наследяваният клас. Как? Чрез overriding.

But class inheritance has some disadvantages too. First, you can’t change the implementations inherited from parent classes at run-time, because inheritance is defined at compile-time. But you can in compile time.
Second, and generally worse, parent classes often define at least part of their subclasses physical representation. Because inheritance exposes a subclass to details of its parent’s implementation, it’s often said that „inheritance breaks encapsulation“. The implementation of a subclass becomes so bound up with the implementation of its parent class that any change in the parent’s implementation will force the subclass to change.

Демек, ако прекаляваме с наследяването, може да достигнем до огромна и тежка „пирамида“ от класове, губейки капсулираност и гъвкавост.
Your classes and class hierarchies will remain small and will be less likely to grow into unmanageable monsters.

Object composition is defined dynamically at run-time through objects acquiring references to other objects. Composition requires objects to respect each others’ interfaces, which in turn requires carefully designed interfaces that don’t stop you from using one object with many others. But there is a payoff. Because objects are accessed solely through their interfaces, we don’t break encapsulation. Any object can be replaced at run-time by another as long as it has the same type. Moreover, because an object’s implementation will be written in terms of object interfaces, there are substantially fewer implementation dependencies.

Демек, трудността тук е да използваме инджектнатите обекти правилно, според интерфейсите им, тоест и самите интерфейси трябва да са добре направени. Предимството на composition е, че не се нарушава капсулацията, всеки обект е само за себе си, и най-вече – взаимозаменяемостта им на база че са от общ тип (имат общ интерфейс). Демек, не „разширяваш“ даден клас чрез наследяване, а подаваш готов обект от даденият клас, на друг обект.

That leads us to our second principle of object-oriented design: Favor object composition over class inheritance.

Delegation

Това е принцип, който използва composition, на практика постига същият ефект като inheritance но го прави не като наследява даден клас и използва неговите методи и пропъртита с this или self, а получава готов обект като параметър и използва неговите такива.

Relating Run-Time and Compile-Time Structures

An object-oriented program is a run-time structure, that often bears little resemblance to its code structure. The code structure is frozen at compile-time; it consists of classes in fixed inheritance relationships. A program’s run-time structure consists of rapidly changing networks of communicating objects. In fact, the two structures are largely independent. Trying to understand one from the other is like trying to understand the dynamism of living ecosystems from the static taxonomy of plants and animals, and vice versa.
Демек, структурата от класове е все едно енциклопедията с растения и животни, но самата програма е вече съвкупност от динамични обекти, които комуникират помежду си, зависят един от друг…, и това е все едно една жива екосистема, една гора например.

A design that doesn’t take change into account risks major redesign in the future.

Object Pool design pattern

The general idea for the Connection Pool pattern is that if instances of a class can be reused, you avoid creating instances of the class by reusing them.

Основната идея е да имаме дадена data structure, например масив, която да съдържа готови обекти, които можем да използваме когато ни трябват, вместо да създаваме нови, особено когато са „expensive to create“.

Но идеята е както просто да съхраняваме дадената data structure но и да я менажираме като например колко максимално да са обектите, маркиране като „ангажирани“ на тези, които изпозваме, връщане в изходно състояние (reset) на обект, който освобождаваме, унищожаване ненужните и т.н…

Basically, an Object pool is a container which contains some amount of objects. So, when an object is taken from the pool, it is not available in the pool until it is put back.

В този смисъл, Object Pool design pattern е близък по идея с Prototype design pattern, защото елиминира инстанцирането (когато е възможно), което е по принцип „скъпо“.

Както и на Multiton design pattern и до Singleton design pattern, защото предлага кеширане на обекти вместо пре-създаването им всеки път когато ни трябват, но с разликата че Singleton се използва за един обект, от един клас, а не много както тук.

Object pools (otherwise known as resource pools) are used to manage the object caching. A client with access to a Object pool can avoid creating a new Objects by simply asking the pool for one that has already been instantiated instead.

Този design pattern може най-просто да се реализира програмно с помощта на един клас ObjectPoolClass да гарантираме, че ще имаме само един object pool, този singleton клас ще има едни private масив $objects, и ще имаме и например два метода acquire() и release() за извличане на нужният обект и за унищожаване на ненужен.

При тези два метода можем да използваме Factory design pattern, подавайки името на нужният клас, чиито обект ни трябва.

Важно е да се знае, че когато вземем нужният ни обект, друг клиент не трябва да може да го взема, тоест трябва например някак да го маркираме като зает.
A client of the pool will request an object from the pool and perform operations on the returned object. When the client has finished, it returns the object to the pool rather than destroying it; this can be done manually or automatically.

The object pool design pattern creates a set of objects that may be reused. When a new object is needed, it is requested from the pool.
1. If a previously prepared object is available it is returned immediately, avoiding the instantiation cost.
2. If no objects are present in the pool, a new item is created and returned.
3. When the object has been used and is no longer needed, it is returned to the pool, allowing it to be used again in the future without repeating the computationally expensive instantiation process. It is important to note that once an object has been used and returned, existing references will become invalid.

И разбира се, когато го „върнем“ първо трябва да го размаркираме като свободен, и непременно да го върнем в „чисто“ /pristine/, първоначално състояние за следващите „ползватели“.

Care must be taken to ensure the state of the objects returned to the pool is reset back to a sensible state for the next use of the object, otherwise the object may be in an state unexpected by the client, which may cause it to fail.

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

Това важи особено когато даденият обект съдържа поверителна информация.

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

Monostate design pattern

Много подобен на Singleton design pattern, с разликата че при Singleton имаме използване само на един обект от клас, а тук имаме много обекти от даден клас но използват една и съща, споделена информация, което се постига с използването на static class properties.

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

The Monostate pattern is usually referred to as syntactic sugar over the Singleton pattern or as a conceptual Singleton.

This is accomplished mostly by using static data members.
One of the most important feature is that it’s absolutely transparent for the users, that are completely unaware they are working with a Monostate. Users can create as many instances of a Monostate as they want and any instance is good as another to access the data.

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

Демек, класът е един, неговите обекти са много, просто променяйки едно static property, тази промяна важи за всички обекти на даденият клас.

Mediator design pattern

Идеята на този design pattern е да създаде нещо като „контролна кула“ или централизиран начин отделни обекти да комуникират и работят заедно.

Един прост пример би бил чат програма с много участници, всеки от които ще приемем, че са отделни обекти, наследяващи или имплементиращи общ абсклас или интерфейс.
Те поотделно не се знаят един друг. Самият чатруум ще е нашият Медиатор, който ще разпределя съобщенията от, и за всеки участник.

Друг пример би бил различните графични среди, или например една HTML форма с различни контроли, които са отделни обекти но не се знаят един друг. При клик на радио бутон например, искаме неговият лейбъл да се покаже в текстово поле. Клас Dialog например може да организира взаимодействието между елементите, действайки като медиатор.

Mediator design pattern силно напомня на Observer design pattern, в този смисъл, че с Observer можем да реализираме програмно идеята на Mediator.

Друг възможен начин да реализираме програмно Mediator е като например имаме двата „участника“, между които искаме да посредничим, и единият вика метод на другият, като му предава например съобщението си и също и себе си (this), за да знае получателят кой го „безпокои“.
Ясно е, че при този начин трябва с абсклас/интерфейс да зададем обща структура на отделните участници.

Mediator design pattern също може да използва и Adapter design pattern за да „напасне“ комуникацията между „участниците“.

Advantages

Comprehension – The mediator encapsulate the logic of mediation between the colleagues. From this reason it’s more easier to understand this logic since it is kept in only one class.

Decoupled Colleagues – The colleague classes are totally decoupled. Adding a new colleague class is very easy due to this decoupling level.
Mediator design patter намалява coupling (зависимостта) между класовете на участниците, защото няма да има нужда те да „знаят“ един за друг и да „се съобразяват“ един с друг, за да могат да комуникират.

Simplified object protocols – The colleague objects need to communicate only with the mediator objects. Practically the mediator pattern reduce the required communication channels(protocols) from many to many to one to many and many to one.

Limits Subclassing – Because the entire communication logic is encapsulated by the mediator class, when this logic need to be extended only the mediator class need to be extended.

Disadvantages

Complexity – in practice the mediators tends to become more complex and complex. A good practice is to take care to make the mediator classes responsible only for the communication part. For example when implementing different screens the the screen class should not contain code which is not a part of the screen operations. It should be put in some other classes.

The mediator pattern is used to takes the role of a hub or router and facilitates the communication between many classes. A similarity can be made with the database systems. The mediator transform a hard to implement relation of many to many, where each calls has to communicate with each other class, in 2 relations, easy to implement, of many to one and one to many, where the communication is handled by the mediator class.

An inflexible way is to define a set of interacting objects ( Colleague1, Colleague2 ,…) by referring to (and update) each other directly, which results in many interconnections between them.

The Mediator design pattern provides a solution:
Define a separate Mediator object that encapsulates how a set of objects interact.
Objects interact with a Mediator object instead of interacting with each other directly.

Демек, „Участник 1“ иска да каже нещо на „Участник 2“. Не му го казва директно, а казва на Медиаторът да го стори.

Литература:

https://www.oodesign.com/mediator-pattern.html

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

Visitor design pattern

Идеята на този design pattern e да може ако искаме да добавяме още функционалност към даден обект (Component), да не го дописваме него (обектът), а да имаме друг клас (Visitor), на който да подаваме този обект, и там, използвайки методите му (на предаденият Component) да създадем допълнителната логика.

Или даже по-скоро изцяло да разделим даден клас на части, например оставяйки единият да отговаря само за данните, а в други (visitors) да имаме различните логики за работа с тези данни.
In object-oriented programming and software engineering, the Visitor Design pattern is a way of separating an algorithm from an object data structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying the structures. It is one way to follow the open/closed principle.

Казах „подаваме“, но имах предвид, че по-скоро приемаме „посетителят“ и му подаваме сами себе си с $this с метод, обикновено наречен accept()

public function accept (Visitor $visitor): void
{
    $visitor->visitConcreteComponent($this);
}

Ето как Visitor обектът бива допуснат в нашият Component и той сам си взема дадената инстанция ($this) с помощта на свой метод visitConcreteComponent() и го използва за да създаде допълнителна функционалност в себе си (в класът Visitor).

Този design pattern е едно типично превъплъщение на Open Close SOLID principle и много напомня на Adapter или Decorator и други, на които подаваме даден обект и те го „доразвиват“.

…the visitor design pattern is a way of separating an algorithm from an object data on which it operates…

По този начин основният клас Component може спокойно да остане максимално прост, а за него да създадем N на брой Visitor класа, които да му добавят нужната допълнителна логика. Особено когато това се налага често.

Най-малкото, което печелим е, че няма да претоварим Component класа и да нарушим Single responsibility SOLID принципа, както и няма да му пишем наследници, което значи, че ще използваме composition over inheritance.

Ето я в основни линии идеята на Visitor design pattern – да можем да „разхвърляме“ логиката на даден клас по други класове (Visitors), а не да го претоварваме като пишем цялата логика само в него.

Отделно, представете си че имаме даден алгоритъм, който трябва да реализираме в повече от един клас. Във всеки ли да го реализираме?
Не е ли по-елегантно да го реализираме в един Visitor клас,
и с accept()-овете на тези отделните, повече от един Component-и,
да му ги подаваме, и той (Visitor-ът) да ги „посещава“ и да реализира тази логика, използвайки ги.

Един Component разбира се може да има много Visitor-и. Даже не „да има“, а „да използва“, защото това са два различни класови структури (Component и Visitor), тоест, няма пряка връзка между тях, като наследяване например.

  1. Visitor makes adding new operations easy. You can define a new operation over an object structure simply by adding a new visitor.
  2. A visitor gathers related operations and separates unrelated ones. Демек, във отделни Visitors можеш да си капсулираш сходни по смисъл функционалности.
  3. Visitor can visit objects that don’t have a common parent class, as long as it can use the Component functionality.
  4. Accumulating state. Visitors can accumulate state as they visit each element in the object structure. Without a visitor, this state would be passed as extra arguments to the operations that perform the traversal, or they might appear as global variables. Visitor-ите могат да акумулират информация от отделните обекти, които му се подават.
  5. Breaking encapsulation. Visitor’s approach assumes that the ConcreteElement interface is powerful enough to let visitors do their job. As a result, the pattern often forces you to provide public operations that access an element’s internal state, which may compromise its encapsulation.
    Като недостатък, понеже Visitor-ите трябва да могат да използват public функционалността на подаваните им обекти, може да се наложи те (използваните обекти) да имат повече public методи и пропъртита, което може да намали data hiding.

Литература:

https://refactoring.guru/design-patterns/visitor/php/example

Null object design pattern

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

Искаш да лае – използваш обект Куче от тип Животно и му викаш метод doSound(), който метод лае.
Искаш да мяука – използваш обект Котка от обект Животно и му викаш метод doSound(), който метод мяука.

А не да проверяваш „ако си куче, изпълни doBauBau(), ако си котка изпълни doMiauMiau()…“

Tell, don’t ask.

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

Например имаме електронна игра с 4 копчета за команди – Up, Down, Left и Right, с които местим нещо по екрана. И искаме за всеки случай, ако например натиснем 2 копчета и т.н… да подадем команда за… невалидна команда, демек „не прави нищо“.

Тук идва идеята на Null object design pattern.

Подаваме обект, който е със същата структура като останалите 4 но методите му не вършат нищо. По този начин няма нужда да имаме разни проверки дали командата е валидна, дали е Up, Down…. и тогава да свършим нещо.

Просто като подадем пак НЕЩО, което обаче не върши НИЩО, постигаме същият ефект и то много по-елегантно и без проверки и т.н…

Литература:

https://docs.php.earth/php/ref/oop/design-patterns/null-object

Iterator design pattern

Като кажем Итератор какво всъщност имаме предвид?
Това е обект, имплементиращ IteratorInterface, който интерфейс декларира какви методи да има даденият Итератор. Те разбира се трябва да са едни и същи откъм signature, но различни откъм имплементация.

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

И поради това, че отделните даннови структури могат да се итерират по различен начин, не е лоша идея всяка от тях да си върви и със свой Итератор. Примерно, ако имаме клас, който представлява дадена даннова структура, може с интерфейс да го задължим да има и setter, който да му задава Итератор…

Идеята на този design pattern е да имаме свой и също така стандартизиран начин да итерираме дадени data структури като масиви (едномерни или двумерни), обекти и т.н…

Ако например имаме едномерен масив, многомерен масив и масив с обекти, за да ги изциклим трябва да имаме три отделни foreach цикъла.
Ако имаме и трети вид структура – четири отделни foreach цикъла… и т.н…

Отделно, трябва да знаем и структурата на отделните data structures…

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

Другият вариант е точно идеята на Iterator design pattern.

Да имаме обект (Iterator), на когото подаваме структурата, която ще циклим.

И понеже можем да имаме повече различни итератори – ще имаме интерфейс (IteratorInterface), и всеки Iterator клас ще го имплементира, като ще има методи като например: hasNextElement(), getNextElement() и т.н…
Полиморфизъм.

Демек, итераторът е длъжен да може и да връща един по един елементите на циклената data структура, и да следи индексът (броячът) на текущият връщан елемент.

Ето един прост пример:

Ще създаваме N на брой обекта Book и ще ги пълним например в масив.

$books = new BookList();
$books->addBook(new Book('Voina i Mir', 'Lev Tolstoy'));
$books->addBook(new Book('Pod Igoto', 'Ivan Vazov'));
$books->addBook(new Book('Mamino detentse', 'Luben Karavelov'));

Подаваме масивът с обекти $books на даден итератор.

$booksIterator = new BookListStraightIterator($books);

Циклим.

while ($booksIterator->hasNextBook()) {
$book = $booksIterator->getNextBook();
echo "getting next book with iterator : \n";
echo $book->getAuthorAndTitle();
}

hasNextBook() ще прави проверка дали има още елементи и ще връща true/false.

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

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

„Provide a way to access the elements of an aggregate object sequentially
without exposing its underlying representation.“
[GoF]
Защо „without exposing its underlying representation“, демек без да знаем и да работим със самата структура на data структурата?
Защото както се вижда, използваме допълнителни методи, които да „свършат работата“ просто казано. И тези допълнителни методи са специфични за всяка data структура, и са „заповядани“ с интерфейс. И те просто казват: „има ли следващ? Да. Дай ми го… Има ли следващ? Да. Дай ми го…“ без да те интересува самата му структура както и какво точно се има предвид под „следващият“.

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

Отделно, така „скриваме“ data структурата от използващите я „клиенти“. „Клиентът“ е този който цикли, заради него е цялата дандания. За да може да цикли без да знае повече за това, което цикли от „дай ми следващият, дай ми следващият…“.

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

Също и може да си напишем итератор-филтър, който да ни връща само нужните елементи, съдейки по дадена логика…

По колкото различни начини може да искаме да изциклим дадена data структура, толкова различни методи трябва да имаме в нея (data структурата). Ясно е колко много това може да я усложни и уголеми, от там – нарушавайки Single responsibility principle.

Има и друг начин да приложим идеята на Iterator design pattern.

Пак създаваме колекция от книги $book, но вместо да я подаваме на итератор като BookListStraightIterator, в нея (в класа и) си задаваме интератор.
Тоест, класът Books ще има метод:

public function createStraightIterator(): BookListStraightIterator
{
    return new BookListStraightIterator($this);
}

И циклим така:

$straightIterator = $booksCollection->createStraightIterator();

while ($straightIterator->hasNext()) {
    echo $straightIterator->next() . "\n";
}

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

Но не нарушаваме ли Single Responsibility SOLID принципът…

Три ползи от използването на Iterator design pattern

Този design pattern е фокусиран изцяло и само върху изциклянето на дадените data structures. И по-точно – да капсулира това изцикляне чрез създаване на допълнителна функционалност и разбира се – чрез използването на тази допълнителна функционалност.

Като допълнение към горното, можем отделно да имаме и функционалност за това какво да правим с всеки текущ елемент. В смисъл, че може да имаме функционалност например за изтриване на елемент – remove()…

Също, важно е да се знае, че Iterator design pattern няма отношение с реда на елементите в колекцията, нито трябва да дава функционалност за сортиране.

  1. Скрива структурата на това, което циклим от външният свят, от клиента, когато искаме да го циклим. Демек, клиентът (циклещият) няма нужда да знае как точно да изцикли структурата, няма нужда да знае масив ли е и т.н…. и отделните и елементи… Просто казва „докато не свършат, давай ми елементите един по един…“
  2. Lazy evaluation – „не ми ги стоварвай всички наведнъж, давай ми ги един по един. Дай първият, аз ще ти кажа кога искам вторият, третият…“
    Така теоретично можем да циклим дори и безкрайни структури.
  3. Имаш по-добър контрол върху самото циклене – можеш да знаеш къде си, да паузираш, да продължиш… не че с обикновено циклене не можеш също, но тук можеш по-гъвкаво да зададеш например някаква логика за това „кой да е следващият например“ и т.н…
    При обикновеното циклене просто ти ги подава така както са сортирани, това е единствената логика, и ако искаш допълнителна логика – може разбира се но трябва да е вътре в цикъла и трябва да знаеш структурата на data структурата… връщаме се на точка 1.

Видове Iterator design pattern

  1. На база това кой контролира цикленето, биват internal и external iterator. При internal самият итератор контролира кой е следващият, при external – използващият клиент. Демек, при external iterator ние като клиент трябва да задаваме броячът и по този начин да казваме кой е следващият елемент, който ни трябва.
    Демек, по-горе разгледаните примери са internal iterator, защото вътре в итератора контролираме кой ще е следващият върнат елемент.
  2. Кой задава и определя самата логика за изцикляне – итераторът или клиентът?
    Когато итераторът само следи за това кой е текущият елемент съхранявайки само броячът, a клиентът цикли, такъв итератор се нарича cursor.
    We call this kind of iterator a cursor, since it merely points to the current position in the aggregate.
    Клиентът сам си вика методи като getNext() например когато иска да си вземе текущият за дадената итерация елемент. И getNext() ще зададе новата стойност на брояча.
  3. Колко надежден е един итератор? Какво се има предвид под „надежден“? Има се предвид ако например по време на циклене внасяме различни промени в data структурата (добавяне на елементи, триене…) дали това няма да се отрази негативно на цикленето, например в реда, в това кой е следващият…
    Итератор, който е надежден в това отношение, и то без заобиколни мерки като копиране на елементи например, се нарича „robust iterator“.
    Например като актуализираме броячът по начин такъв, че например да не върнем два пъти даден елемент, или да не върнем null, ако елементът е бил изтрит и т.н…
    On insertion or removal, the aggregate either adjusts the internal state of iterators it has produced, or it maintains information internally to ensure proper traversal.
  4. Minimal iterator – по принцип минималният итератор трябва да съдържа такива методи: first(), next(), isDone() и currentElement().
    Но може да имаме и повече методи като например previous() и т.н…

Литература:

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

Template method design pattern

Много лесен и приятен design pattern. И наистина си заслужава името.

Цялата му идея се състои в това в един абсклас (но може и не абстрактен), ще го наричаме „base class“, да имаме един или повече final методи (важно е да не могат да се овъррайдват), който/които ще са т.н. „темплейти“, тоест, подобно на HTML темплейт, те ще викат други методи и ще сглобяват нещо цялостно.

В абскласа също трябва да има един или повече абстрактни методи, който да се дефинират в класовете наследници. Ще ги наричаме „hook methods“, защото на тях ще „закачаме“ подобно на куки, какво трябва да правят по-късно.

Но също и те (тези, абстрактните методи) да бъдат викани и в „темплейт метода“. В който темплейт метод може да има и друга логика разбира се, могат да се викат и други методи… Може и да не се викат всички от тези абстрактните (the hooks), може да се викат в различен ред, по колкото пъти трябва и т.н…

Идеята е те (hooks) да бъдат дефинирани в класовете наследници, за да може като инстанцираме някой от наследниците и извикаме темплейт метода от абскласа (родителя), той да може в себе си да ги извика тях (hooks) и така подобно на темплейт, да сглоби цялата логика, използвайки ги така да се каже, за да „попълни“ себе си с тях.

Отделните „парчета“ ще варират според как са дедфинирани в класа/класовете наследници, но схемата по-която се викат е една и е зададена в метода-темплейт.

Ето един крайно опростен пример:

<?php
abstract class PatapanAbstract
{
    final public function templateMeto1(): void
    {
        // some other logic or methods calls...

        $this->someSimpleMeto1();

        // some other logic or methods calls...

        $this->absMeto1();

        // some other logic or methods calls...

        $this->someSimpleMeto2();

        $this->absMeto2();

        // some other logic or methods calls...

        $this->someSimpleMeto2();
    }

    private function someSimpleMeto1(): void
    {
        echo 'Hello, this is private simple meto 1';
    }

    private function someSimpleMeto2(): void
    {
        echo 'Hello, this is private simple meto 2';
    }

    abstract protected function absMeto1();
    abstract protected function absMeto2();
}

class PatapanConcrete1 extends PatapanAbstract
{
    public function absMeto1(): void
    {
        echo 'This is meto 1';
    }

    public function absMeto2(): void
    {
        echo 'This is meto 2';
    }
}

$p1 = new PatapanConcrete1;
$p1->templateMeto1();

Ще имаме резултат:

Hello, this is private simple meto 1
Hello, this is meto 1
Hello, this is private simple meto 2
Hello, this is meto 2
Hello, this is private simple meto 2

Вижда се ясно как в „PatapanAbstract“ е създаден нещо като темплейт, а какво да има в отделните „парчета“, се дефинира в класа наследник/наследници.

The template method is a method in a superclass, usually an abstract superclass, and defines the skeleton of an operation in terms of a number of high-level steps. These steps are themselves implemented by additional helper methods in the same class as the template method.

Отделно, може и отделните hook методи да не са абстрактни, тоест да не е задължително да ги дефинираме в класовете-наследници. Може да са обикновени празни методи.

The helper methods may be either abstract methods, in which case subclasses are required to provide concrete implementations, or hook methods, which have empty bodies in the superclass. Subclasses can (but are not required to) customize the operation by overriding the hook methods. The intent of the template method is to define the overall structure of the operation, while allowing subclasses to refine, or redefine, certain steps.

И друго много съществено за този design pattern e, че логиката в темплейт метода е твърдо зададена и не може да се променя в наследниците. Затова и темплейт метода трябва да е задължително final.
Динамична е логиката по отделните „парчета“ – the hooks.

Също, понеже главният клас (base class) се явява „high-level module“ за наследяващите го „low-lewel“ модули, и не зависи от тях както и ги оставя те да задават своята логика, можем да кажем, че Template method design pattern е пример за „Inversion of control principle“.

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

Design Principle vs. Design Pattern

Първо, важно е да споменем две понятия и да разбере разликата между тях –
Design Principle и Design Pattern.

Design Principle е така да се каже по-общото. Това са общи, добри практики, които е добре да се спазват, независимо от езика за програмиране и не предлагат конкретни начини на приложение. Това са чисто абстракни принципи.

Един пример за design principles са например SOLID принципите.

Design patterns са вече по-конкретни решения на различни проблеми от ООП, но спазвайки design principles. Например, искаме от даден клас да имаме само един обект – използваме Singleton design pattern, ето това е една design pattern.

Design patterns са отново независими от езикът за програмиране както design principles, но са по-скоро практическото приложение на design principles.

Също, когато прилагаме или създаваме design pattern, трябва винаги да внимаваме да не нарушаваме design principle. По принцип един клас винаги се пише така, че да има една „основна отговорност“, заради която бива писан, и отделно – различни, така да се каже „помощни“ функционалности, като например логери, емайл, спелчекъри…
За такива е идеята – да бъдат отделени като отделни класове и да бъдат инджектвани.

Литература:

https://www.tutorialsteacher.com/articles/difference-between-design-principle-and-design-pattern

Inversion of Control principle (IoC), Dependency Inversion principle (DIP) и Dependency Injection (DI)

Dependency Injection (DI)

Когато можем да инджектваме даден обект в друг обект.

Така се реализира design принципът Inversion of Control, демек – на практика реализиране на Single Responsibility SOLID принципът.

Types of Dependency Injection
  1. Constructor Injection – през конструктора, демек инджектваният обект се подава oще на конструктора на основният клас и се сетва като пропръти още при инстанциране на основният обект.
  2. Property (Setter) Injection – идеята е подобна на горната, пак инджектваният обект да бъде сетнат на клас пропърти, но не през конструктора, а през сетър.
  3. Method Injection – тук имаме ситуация, подобна на Property (Setter) Injection но с разликата, че основният клас имплементира интерфейс, който го задължава (него, основният) да има метод, който да играе ролята на сетър, и по този начин задължава основният клас да приеме даден обект.
    Например клас Car имплементира интерфейс EngineMountable, в който има метод engine() и по този начин класът Car e длъжен да има инджектнат обект за двигател.
    Добър въпрос би бил, ако Car наследява например абсклас, не може ли в него да е изнесен абстрактен сетер/сетери, които да играят същата роля, тоест вместо интерфейс да имаме абсклас.
    Не съм съвсем наясно с идеята на този тип injection, признавам си. Може би идеята е да се зададе някакъв централизиран начин за инджектване на обекти, ако имаме абсклас, или вероятно ако е интерфейс, да задължи имплементиращите обекти да имат такива „entry points“ за инджектване на обекти…

Inversion of Control (IoC)

Inversion of Control (IoC) е desing principle, която използва DI, при който идеята е части от цялостната функционалност на даден клас, да се „разхвърля“ или разпредели по други класове, с цел да се спази SOLID Single Responsibility Principle и основният клас да бъде олекотен, и след това когато ни трябват тези „разхвърляни“ функционалности, обекти на тези класове да бъдат инджектвани в основният клас.

Taка се постига и high cohesion вътре в класа както и low coupling между отделните класове.

Dependency Inversion Principle (DIP)

Dependency Inversion Principle (DIP) е SOLID принцип, нещо като продължение или разширение на IoC, в смисъл такъв, че повелява основният клас, в който ще инджектваме обектите на класовете, които сме „разхвърляли“, да може да приема като входни параметри не обекти от конкретен клас, а от различни класове, които обаче екстендват или имплементират даден абсклас или интерфейс.
Демек, „дай ми нещо, с което да отвинтя тази гайка, а не конкретен гаечен ключ“…

Идеята и на двата принципа е да се постигне максимално т.н. „loose coupling“ и максимална кохезия вътре в класа. Припомнете си статията „Cohesion and Coupling“.
Но работата е е там, че само с прилагане на IoC не става, демек само IoC не стига за да се постигне това.

Тоест, основният клас сякаш казва предварително: „не искам конкретна функционалност и обект от конкретен клас, давайте ми обекти, които по принцип ще ми свършат работата“, демек имплементиращи даден интерфейс.

Taка постигаме „high-level modules should not depend on low-level modules“.

Защо?
Защото в случая, high-level модулът при нас е основният клас, а low-level са тези, които му инджектваме, които вършат някаква по-конкретна и специфична работа, тоест са по-ниско в нивото на абстракция.
И основният става по-малко зависим от инджектваните, защото може да приема по-широк диапазон от обекти, а не конкретно само от един клас.

Пример: ти си основният клас и искаш да си викнеш (инджектнеш) майстор да ти смени водомера.
Кога си по-малко зависим (бидейки high-level module) от майстора (който е low-level module)? Ако трябва да можеш да викаш само един конкретен майстор или когато можеш да извикаш всеки майстор, стига да имплементира VodoprovodchikInterface?

Общото между тях?

Общото между IoC и DIP е целта – да се намали зависимостта между отделните класове. Например, да не се създава обект от един клас вътре в друг клас, защото така твърдо свързваме двата класа и единият вече не може без другият, конкретно „другият“, то е ясно, че все някакъв обкет трябва да му инджектнеш, освен ако не си му задал default стойност null.

Демек, когато инджектваме обект, да не очакваме параметърът през който го инджектваме да е от конкретен клас, а да е по-общо – от конкретен интерфейс. Така казваме, че инджектваме не конкретна функционалност, а който по принцип прави това, което ни трябва. Без задължително да е обект от този конкретен клас.

Your code gets decoupled so you can easily exchange implementations of an interface with alternative implementations.

Разликата между тях?

Разликата е, че DIP e по-скоро продължение, надстройка на IoC. Демек, IoC повелчва функционалността на един клас да се „разхвърля“ на под-класове, а DIP повелява това разхвърляне да стане така, че „разхвърляният“ клас да може да приема не конкретни функционалноти, а функционалности по принцип.

Литература:

https://www.tutorialsteacher.com/ioc/introduction

https://www.tutorialsteacher.com/ioc/dependency-inversion-principle

https://www.tutorialsteacher.com/ioc/dependency-injection

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

Memento design pattern

Memento – обект, който служи само за да съхранява ЕДНА „моментна снимка“ (shapshot) на обект, който ще наричаме Originator.

И отделните Memento обекти („mementos“ на Originator обектът) могат от своя страна да бъдат организирани като масив например, където да се трупат и достъпват.

A memento is an object that stores a snapshot of the internal state of another object—the memento’s originator.

Много важно е Memento класът да не допуска друг клас освен Originator да има достъп до отделните запазени състояния (snaphots), демек Originator-ът сам да си управлява така да се каже „миналите състояния“.

Също, класът Memento трябва да има поне една двойка публични сетър/гетър методи, които Orginator да вика, за да може да вземе текущото състояние на данните на Memento, както и да може да възстанови някои от предишните такива. Това ще са така да се каже „прозорци“ на Memento класа.
Да не забравяме, че принципите data hiding и capsulation трябва да се спазват.

Originator – това ще е обектът, на който ще запазваме отделните състояния.
Only the originator can store and retrieve information from the memento, the memento is „opaque“ to other objects.

Оригинаторът ще има един метод (напр. „saveToMemento()“), в който ще се създава обект Memento, на който пък се подава дадената информация (моментна снимка), която ще искаме да съхраним.
Ще има съответно и метод (напр. „restoreFromMemento()“), който ще приема даденото „мементо“, което искаме да възстановим.

CareTaker – това е класът, който ще използваме като „шкафче“ и където ще пазим отделните mementos, например като масив. Originator ще използва този клас за да добавя/извилича отделните mementos.

CareTaker не се интересува и не работи по никакъв начин с информацията, съхранена в него, само я съхранява, добавя и изважда от „шкафчето“ за да я върне на Originator.

Прост пример:

<?php
/**
 * Това ще ни е класът, на когото ще пазим отделните "моментни снимки" 
 * с оглед на това да можем да възстановяваме
 * когато ни трябва. Тоест, заради него ще прилагаме самият Memento Design Pattern.
 * Съществените методи са "saveToMemento()" и "restoreFromMemento()"
 */
class Originator
{
    private string $state;

    // The class could also contain additional data that is not part 
    // of the Memento Design Pattern

    public function set(string $state) : void
    {
        $this->state = $state;
        printf("%s\n\n", 'Originator: Setting state to ' . $state);
    }

    public function saveToMemento() : Memento
    {
        printf("%s\n\n", 'Originator: Saving to Memento.');
        return new Memento($this->state);
    }

    public function restoreFromMemento(Memento $memento) : void
    {
        $this->state = $memento->getSnapshot();
        printf("%s\n\n", 'Originator: State after restoring from Memento: ' . $this->state);
    }
}


/**
 * Този клас ще играе ролята на една прост data container, всеки обект 
 * от който ще е просто една "моментна снимка",
 * по този начин информацията ("моментната снимка") ще е "скрита" от 
 * околният свят (data hidding).
 */
class Memento
{
    private string $snapshot;

    public function __construct(string $snapshotToSave)
    {
        $this->snapshot = $snapshotToSave;
    }

    public function getSnapshot() : string
    {
        return $this->snapshot;
    }
}


/**
 * Това ще е така да се каже "диригентът" на това да създаваме моментни снимки,
 * и да можем да ги възстановяваме.
 */
class Caretaker
{
    private array $savedStates = array();  // This will be the array with the mementos

    private Originator $originator;

    public function __construct(Originator $originator)
    {
        $this->originator = $originator;
    }

    public function takeCare() : void
    {
        $this->originator->set("State 1");

        $this->originator->set("State 2");

        // Push a memento, или още "Запази се себе си в едно Memento и го дай да го пушнем в $savedStates"
        array_push($this->savedStates, $this->originator->saveToMemento()); 

        $this->originator->set("State 3");

        $this->originator->set("State 4"); 

        // Push a memento, или още "Направи си селфи и ми го дай да го пушна в $savedStates" 
        array_push($this->savedStates, $this->originator->saveToMemento()); 

        $this->originator->set("State 5");

        $this->originator->restoreFromMemento($this->savedStates[1]);
    }
}

(new Caretaker(new Originator))->takeCare();

Command design pattern

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
GoF

Представяме си, че имаме една проста програма, например текстов редактор. И той нека има меню с няколко бутончета – New, Open, Save, Save all и т.н…, чиято работа ще е не директно да свършат това, за което за били кликнати, а само да извикат това, което трябва да стане, когато са кликнати, което си е в текст редактора (нека го наречем „бизнес моделът„).

Тоест, идеята е да организираме викането на функционалността в основният обект, не разпределянето и в подкласове.

Единият начин да организираме работата на тези бутончета (нека ги наречем „викачи“) е например да имаме абсклас/интерфейс Button и всяко бутонче да екстендва/имплементира със свой клас. И всеки от тях да вика съответната функционалност в главният обект – „бизнес моделът„.

Това не е фатално въпреки, че може да доведе до експлозия от класове когато имаме нужда от повече и повече бутончета. И стига да внимаваме да не счупим всичко щом променим родителят Button.

Отделно, да не забравяме, че дадена функционалност може да трябва да се вика не само при клик на бутонче, а и например при събития като Ctrl+N, Ctrl+S…
И за всяко от тях ли ще правим клас-викач?

Command pattern казва, всяка от тези отделни команди, като New, Open, Save… трябва да бъдат капсулирани в класове – клас NewCommand, OpenCommand, SaveCommand…
И отделните бутончета или събития (Ctrl+N…), тоест „викачи“, да викат тях, а не директно да викат функционалността на „бизнес модела“.

Тоест, принципът например за Save конкретно, да НЕ Е този

а този

Което ще рече, че този design pattern е подходящ когато трябва да можем по-унифицирано и гъвкаво да можем да викаме функционалност (методи) от даден обект, от повече от един „викач“. Демек, не отделните „викачи“ сами да си викат това, което трябва да викат, а да викат един общ викач, който да вика това, което трябва.

В крайна сметка, дали ще кликна Save дискетката, ще натисна Ctrl+S или ще дам с десен бутон Save контекстното меню, не става ли все едно и също?

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

Също и отделните „викачи“ (бутончета, клавишни комбинации, контекстни менюта…) нямат задължението и нуждата да знаят нещо за „бизнес модела“ (изолация).

Eдно възможно предимство на този design pattern е, че например, щом сме капсулирали отделните команди в общ абсклас, можем да пазим например отделните състояния, да имаме например history, което например би ни помогнало да направим нещо като undo функционалност.

Литература:

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

https://sourcemaking.com/design_patterns/command

https://sourcemaking.com/design_patterns/command/php

Tight coupling and Loose coupling

Един добър пример за loose coupling е например, ако хвърля топка към някого за да я хване. Интересува ме той да я хване, не ми трябва да знам на колко години е, какво е закусвал тази сутрин и т.н… Щом просто свършим работата, колкото и по-малко да знаем един за друг, ето това е loose coupling.

Един пример за tight coupling и даже по-скоро за rigid and fragile код:

class CustomerRepository
{
    private Database $database;     

    public function __construct(Database $database)
    {
        $this->database = $database;
    }

    public function Add(string $tableName, string $customerName) : void
    {
        $this->database->AddRow('John Doe', $customerName);
    }
}

class Database
{
    public function AddRow(string $table, string $value) : void 
    {
        //...
    } 
}

Показаният код е реално напълно редовен, но проблемът може да се види в перспектива, защото ако променим нещо в AddRow() методът на класа Database, това би счупило използването му в CustomerRepository->Add().
Каквото и да било, поредноста на аргументите, типовете на съответните им параметри…

Демек, хвърлям ти топката но дали ще я хванеш…
За да съм сигурен, че ще я хванеш, ще те задължа да имаш съответният метод, със съответният signature.

Добър пример за това как да превърнем горният пример от tight coupled в loose coupled, би бил например да задължим с интерфейс методът AddRow на класа Database да спазва зададен signature.

class CustomerRepository
{
    private IDatabase $database;     

    public function __construct(IDatabase $database)
    {
        $this->database = $database;
    }

    public function Add(string $tableName, string $customerName) : void
    {
        $this->database->AddRow('John Doe', $customerName);
    }
}

interface IDatabase
{
    public function AddRow(string $table, string $value) : void;
}

class Database implements IDatabase
{
    public function AddRow(string $table, string $value) : void 
    {
        //...
    }
}

Можем да си направим извод, че loose coupling не значи непременно по-голяма либералност, а както се вижда в случая – даже обратното. По-скоро стандартизация, защото така премахваме опасността от различни разновидности (по signature например) на методът AddRow()

Демек, от Add() на CustomerRepository викаме директно AddRow() на Database, но той е длъжен да спазва сигнъчъра на интерфейса IDatabase. Като с цел взаимозаменяемост, той може да има различни имплементации.

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

class DiscOperations
{
    public function doDiscWrite(string $data) : bool
    {
        //...
    }
}

class DVDOperations
{
    public function doDVDWrite(string $data) : bool
    {
        //...
    }
}

if (WRITE2DISC) {
    (new DiscOperations)->doDiscWrite($someData);
} else {
    (new DVDOperations)->doDVDWrite($someData);
}

Не би ли по-унифицирано ако например…

interface StorageOperations
{
    public function write(string $data) : bool;
}

class DiscOperations implements StorageOperations
{
    public function write(string $data) : bool
    {
        //...
    } 
} 

class DVDOperations implements StorageOperations
{
    public function write(string $data) : bool
    {
        //...
    }
}

$operator = WRITE2DISC ? new DiscOperations : new DVDOperations;

$operator->write($someData);

Защо? Защото най-малкото можем да си напишем много подобни Оператори, който ще са лесно заменими.

Chain of responsibility design pattern

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request long the chain until an object handles it.

„The pattern frees an object from knowing which other object handles a request.“ Демек, кой точно ще свърши необходимата работа е въпрос на някаква допълнителна логика.

„…and an object in the chain doesn’t have to know about the chain’s structure.“

„Instead of objects maintaining references to all candidate receivers, they keep a
single reference to their successor.“

„Since a request has no explicit receiver, there’s no
guarantee it’ll be handled – the request can fall off the end of the chain without
ever being handled. A request can also go unhandled when the chain is not
configured properly.“

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

Като в Държавната администрация – всеки те прехвърля на другото гише. Или за дадено нещо трябва да минеш през 5 гишета.

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

Затова си правим набор от класове и ръчно задаваме кой след кого е във въпросната „верига на отговорности“.

И подаваме задачата на първия. В него първо проверяваме дали той може да я свърши и ако не – предаваме задачата на следващия…. и така нататък докато попаднем на клас, който може да я свърши. Ако не попаднем – трябва да имаме по default клас, който например да покаже, че не сме обработили задачата.

Примерна програмна реализация

Първо, имаме един интерфейс ChainInterface където имаме декларирани две метода – setNextChainLink() и doTheJob(), личи си от имента им кой какво прави.

В setNextChainLink() ще се задава следващият елемент от веригата, а в doTheJob() ще проверяваш дали е за него работата и ако да – свърши я,
ако не – предай нататък.

Пак повтатям – задаването на „веригата от отговорности“ става предварително и ръчно. Примерно, правим си 5 класа, които трябва да имплементират
интерфейса ChainInterface, и всеки ще има едно private пропърти $nextChainLink където в конструктора, като създаваме този обект, ще му подаваме обект, създаден преди.

Този design pattern е много удобен например за избягване от заплетена логика, вложени IF-ове и т.н… Просто трябва логиката да се организира поточно.

Пример:

interface ChainInterface
{
    public function setNextChainLink(ChainInterface $oChainLink);
    public function doTheJob($jobToBeDone);
}

class Responsible1 implements ChainInterface
{
    private ChainInterface $nextChainLink;

    public function setNextChainLink(ChainInterface $oChainLink = NULL)
    {
        if (empty($oChainLink)) {
            // do smth...
        } else {
            $this->nextChainLink = $oChainLink;
        }
    }

    public function doTheJob($jobToBeDone)
    {
        if ($jobToBeDone['job'] == 'add') {
            return $jobToBeDone['tova'] + $jobToBeDone['onova'];
        }

        if (empty($this->nextChainLink)) {
            return 'Stignahme do kraq i ne namerih ChainInterface, kojto da obraboti tazi zada4a.';     
        }

        return $this->nextChainLink->doTheJob($jobToBeDone); 
    }
}

class Responsible2 implements ChainInterface
{
    private ChainInterface $nextChainLink;

    public function setNextChainLink(ChainInterface $oChainLink = NULL)
    {   
        if (empty($oChainLink)) {
            // do smth...
        } else {
            $this->nextChainLink = $oChainLink;
        }
    }

    public function doTheJob($jobToBeDone)
    {
        if ($jobToBeDone['job'] == 'sub') {
             return $jobToBeDone['tova'] - $jobToBeDone['onova'];
        }

        if (empty($this->nextChainLink)) {
             return 'Stignahme do kraq i ne namerihme ChainInterface, kojto da obraboti tazi zada4a.';
        }

        return $this->nextChainLink->doTheJob($jobToBeDone);
    }
}

class Responsible3 implements ChainInterface
{
    private ChainInterface $nextChainLink;

    public function setNextChainLink(ChainInterface $oChainLink = NULL)
    {
        if (empty($oChainLink)) {
            // do smth...
        } else {
            $this->nextChainLink = $oChainLink;
        }
    }

    public function doTheJob($jobToBeDone)
    {
        if ($jobToBeDone['job'] == 'mul') {
            return $jobToBeDone['tova'] * $jobToBeDone['onova'];
        }

        if (empty($this->nextChainLink)) {
             return 'Stignahme do kraq i ne namerihme ChainInterface, kojto da obraboti tazi zada4a.';
        }

        return $this->nextChainLink->doTheJob($jobToBeDone); 
    }
}

$resp1 = new Responsible1();
$resp2 = new Responsible2();
$resp3 = new Responsible3();

$resp1->setNextChainLink($resp2);
$resp2->setNextChainLink($resp3);

$res = $resp1->doTheJob(array('tova' => 4, 'onova' => 3, 'job' => 'add'));
var_dump($res);

Литература:

https://refactoring.guru/design-patterns/chain-of-responsibility

Strategy design pattern

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

Този шаблон пресъздава SOLID принципът Open-Close Principle.

Характерно за този шаблон е, че се използва принципът Composition, а не Inheritance, тоест отделните „стратегии“ не трябва да бъдат наследявани, а да бъдат добавяни като пропърти на даден централен клас, който ще наричаме Контекстен.

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

Пример:

interface IStrategy
{
    public function showTitle(Book $oBook) : string;
}

class StrategyCaps implements IStrategy
{
    public function showTitle(Book $oBook) : string
    {
        $title = $oBook->getTitle();
        return strtoupper($title);
    }
}

class StrategyExclaim implements IStrategy
{
    public function showTitle(Book $oBook) : string
    {
        $title = $oBook->getTitle();
        return str_replace(' ', '!', $title);
    }
}

class StrategyStars implements IStrategy
{
    public function showTitle(Book $oBook) : string
    {
        $title = $oBook->getTitle();
        return str_replace(' ', '*', $title);
    }
}


class StrategyContext
{
    private IStrategy $strategy;  // Instance of one of the above classes
 
    // 1. Set desired strategy in a private property 
    public function __construct(IStrategy $oStrategy) 
    {
        $this->strategy = $oStrategy; 
    } 

    // 2. Perform selected strategy method 
    public function showBookTitle(Book $book): string
    {
        return $this->strategy->showTitle($book); 
    }
}


class Book
{
    private string $author, $title;

    public function __construct(string $title_in, string $author_in) 
    {
        $this->author = $author_in;
        $this->title  = $title_in;
    }

    public function getAuthor(): string 
    {
        return $this->author;
    }

    public function getTitle(): string 
    {
        return $this->title;
    }

    public function getAuthorAndTitle(): string 
    {
        return $this->getTitle() . ' by ' . $this->getAuthor();
    }
}

// Предварително създаваме набор от различни стратегии
$strategyContextC = new StrategyContext(new StrategyCaps());
$strategyContextE = new StrategyContext(new StrategyExclaim());
$strategyContextS = new StrategyContext(new StrategyStars());

// Върху този обект ще прилагаме различните стратегии
$book = new Book('Nemili Nedragi', 'Ivan Vazov');

echo "\n\ntest 1 - show name context C \n";
echo $strategyContextC->showBookTitle($book);
echo "\n\n";

echo "\n\ntest 2 - show name context E \n";
echo $strategyContextE->showBookTitle($book);
echo "\n\n";

echo "\n\ntest 3 - show name context S \n";
echo $strategyContextS->showBookTitle($book);
echo "\n\n";

// Може и без контекст
$strategyContextC1 = new StrategyCaps();
echo $strategyContextC1->showTitle($book);

Виждат се отделните „стратегии“, вижда се обектът, върху който ще ги прилагаме.

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

Observer design pattern

Най-просто, идеята е даден обект/обекти, които ще наречем Subjects, когато им бъде извикан някой от методите, този метод/методи допълнително да извикат някаква функционалност (методи) на други обекти, наречени Observers.

Примерно имаш обект Продукт, който има разни пропъртита (цена, наличност, описание…) и примерно трябва да се промени цената (да поскъпне). И примерно трябва освен да UPDATE-неш таблицата в базата, трябва и да изпратиш емайл на всички клиенти за да има кажеш, че е поскъпнал.

Има 2 вида обекти (от два класа) – Subject и Observer.

На всеки Subject му се задават 1 или N на брой Observer (като масив) с метода attach(), а могат и да се махат с detach().

Kaк се attach(IObserver $observer)-ват? Подаваш го на Subject->attach(), изцикляш масива с Observers-те му добавени до тук, и проверяваш дали вече го имаш този обект добавен. И естествено, ако не – push-ваш го към края на масива.

Как се detach(IObserver $observer)-ват? По подобен начин, пак изцикляш масива с Observers и проверяваш дали го имаш този обект, добавен към Observers на дадения Subject.
И ако да – unset($this->observers[$oKey])-ваш го.

Важен е Subject, него ще следим дали се променя. Въпросното „следене“ е просто ако примерно със setter се зададе стойност на някое property, и ако искаш Observer-те му (на даденият Subject) да „разберат“, ги изцикляш с foreach() и за всеки Observer му предаваш (използвайки update(ISubject $subject)) текущата инстанция на Subject, с $this (себе си).

Както казахме, това „предаване“ на Subject на Observer става с метод на Observer-a update(ISubject $subject), който приема $this (Subject-a) и вече има копие на този Subject.
Демек, когато нещо се случи в Subject, при условие че имаш масив с Observers, които така да се каже, да го „следят“, изцикляш този масив с Observers, и при всяка итерация извикваш методът update($this) на конкретният Observer.

В Subject-ът трябва да имаме задължително 3 метода, с които ще му задаваме (на Subject-ът) кои ще са му Observer-итер, които ще пълним в private масив към Subject-а.

attach(IObserver ) – подаваш му обект-наблюдател, който бива добавен към private масива с останалите наблюдатели за този субект.

detach(IObserver ) – пак подаваме обект-наблюдател но с цел да го махнем от този private масив.

notify() – извиква се ръчно когато настъпи „наблюдавано събитие“, изциклят се всичките наблюдатели, атачнати към даденият Subject, и на всеки Observer му се вика методът update(ISubject).

Oчевидно е, че всеки Observer трябва да има задължително метод update(), който ще съдържа какво трябва да се случи, когато го извикаме, тоест, когато настъпи „събитие“.
Който пък от своя страна ще бъде извикан в методът notify() на Subject-ът, когато настъпи т.н. „събитие“ и изциклим масивът с Observer-ите.

Най-просто казано, задачата на notify() на даденият Subject e да каже на атачнатите Oserver-и „действайте“.

Пример:

/**
 * Понеже може да имаме много обзървъри, нека ги задължим
 * да имат метод update() за да го викаме унифицирано
 */
interface IObserver
{
    public function update(ISubject $oSubject) : void;
}

/**
 * Понеже можем да имаме много субекти (наблюдавани обекти),
 * нека ги задължим да имат методи за:
 *    - добавяне на обзървър към даденият обект;
 *    - махане на обзървър от даденият обект;
 *    - оповестяване на обзървърите, закачени към субекта. Този метод
 *      трябва изрично да се извика при настъпване на дадено събитие.
 */
interface ISubject
{
    public function attach(IObserver $oObserver) : void;
    public function detach(IObserver $oObserver) : void;
    public function notify();
}

class FirstExampleObserver implements IObserver
{
    /**
     * Когато в някой Субект настъпи някаква промяна, той (Субектът) 
     * ще вика този метод на всичките си обзървъри.
     * Примерно като ги изцикля ако са в масив.
     * И така образно казано ще им казва, че с нещо се е променил, 
     * да знаят. И трябра да им предава на всеки по едно $this
     *
     * В нашия случай този метод не прави нищо кой знае какво, просто 
     * показва промените.
     */
    public function update(ISubject $oSubject): void
    {
         echo "\n\n";
         echo "IN PATTERN OBSERVER - NEW GOSSIP HAS ARRIVED*\n";
         echo $oSubject->getFavorites() . "\n";
         echo "\n\n";
    }
}

class FirstExampleSubject implements ISubject
{
    private string $favorites = '';
    private array $observers = array();  // Array with observers for the given "FirstExampleSubject" object

    /**  
     * The "subject" must maintain a list (array) of "observers" (or dependents) objects
     * and notifies them when a changes in it (the subject) happens,
     * usually by calling one of their methods (in this case - update()).
     *
     * Т.е. с attach() задаваш обзървърите на даден субект.  
     */
    public function attach(IObserver $oObserver): void
    {
        $this->observers[] = $oObserver; 
    } 

    /**
     * Това е обратното на attach(), когато искаш да махнеш даден обзървър от субекта.
     * Предаваш като параметър обекта-обзървър, изцикляш на субекта всички обзъръври,
     * при всяка итерация проверяваш всеки дали не е този, който си предал (за махане), и го unset()-ваш ако да.
     */
    public function detach(IObserver $oObserver): void
    {
        foreach ($this->observers as $oKey => $oVal) {
            if ($oVal == $oObserver) {
                unset($this->observers[$oKey]);
            }
        }
    }

    /** 
     * Този метод се вика, когато искаш да "информираш" всички обзървъри на даден субект.  
     * Той ги изцикля и за всеки вика update() като предава по едно $this  
     * Всеки обзървър тряба да има метод update(), което се гарантира с интерфейс.  
     */
    public function notify(): void
    {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    } 

    /**  
     * Единият начин за "нотифайване" на обзървъра е  
     * като директо се извика notify() в даденият метод  
     */ 
    public function updateFavorites(string $fav = ''): void
    {
        $this->favorites = $fav;
        $this->notify();
    }

    /**  
     * Може и така, с "чейване на влакче".  
     */
    public function setFavorites(string $fav): self
    {
        $this->favorites = $fav;

        return $this;
    } 

    /**  
     * Може и да не нотифайваме ;-P
     */ 
    public function getFavorites(): string
    {
        return $this->favorites; 
    }
}

$kljukar1 = new FirstExampleSubject();

$patternGossipFan = new FirstExampleObserver();

$kljukar1->attach($patternGossipFan);

$kljukar1->updateFavorites('Pena se razvede');
$kljukar1->updateFavorites('Ivan izneveri na jena si');
$kljukar1->updateFavorites('Pe6o si izgoni svekyrvata obratno v Kaspichan');
$kljukar1->updateFavorites('Kak Minka ne moje da gotvi');

$kljukar1->setFavorites('Gosho krade nafta')->notify();  // Може и така, с "влакче".

$kljukar1->detach($patternGossipFan);

$kljukar1->updateFavorites('Tova veche ne go nabludavame, zashtoto detach-nahme observera');

Low coupling between Subject and Observer – Субектът има списък със своите Обзървъри, които са му атачнати, но само толкова. Те са съвсем отделни обекти, от един общ интерфейс, а не част от него. Субектът задължително трябва да може да работи безпроблемно дори и без никакви Обзървъри. На Обзървърите трябва да се гледа чисто и просто като някакви придатъци, без който може и в никакъв случай да няма никава зависимост, поради която промени в едното да счупят другото.
Типичен пример за Composition over inheritance.

Също, трябва да се внимава кога Субектът вика своите Обзървъри. Трябва Субектът да е в т.н. „consistent state“, тоест когато информацията му е вече променена, например цената на продукта, заявките към БД са къмитнати… и т.н…, а не преди това. Затова може би най-добре е това викане на Обзървърите да става например в деструктора.

Също, интересно е да се спомене, че даден Обзървър може да бъде нотифайван от не само един Субект, тоест може да е Обзървър на повече от един Субект.

Тhe push and pull models

Отделно, трябва да споменем, че има два модела, или по-скоро две разновидности на Observer design pattern – push model и pull model.

При push model, Субектът сам изпраща цялата информация към Обзървърите си независимо дали те я искат или не.

При pull model Субектът изпраща само сигнал или флаг към Обзървърите си, и те решават дали сами да си изтеглят информацията. Но на каква база да решат? Демек, ти им казваш: промених се, ъпдейтнете се и вие, но те ще кажат: ОК но кажи какво точно си се променил за да можем да решим дали тряба да се променяме… Демек, не ни казвай само да се променим и ние, кажи ни защо, как, какво при теб се промени…

Implementations of the Observer pattern often have the subject broadcast additional information about the change. The subject passes this information as an argument to Update. The amount of information may vary widely.

At one extreme, which we call the push model, the subject sends observers detailed information about the change, whether they want it or not. At the other extreme is the pull model; the subject sends nothing but the most minimal notification, and observers ask for details explicitly thereafter.

Вижда се, че при pull model Обзървърите трябва да знаят повече за Субекта си отколкото при push model.

UML диаграми и видове отношения между класовете

Association – чисто семантична връзка от например тип HAS-A, например доктор има пациенти и т.н… Програмно може например да се реализира като един клас Доктор има едно или повече пропъртита – обекти от клас Пациент.

Dependency – тук имаме директна зависимост между различни елементи, които не могат да съществуват един без друг. Например наследяване на класове, имплементиране на интерфейси… Този тип отношение може да се раздели на две: specification и generalization.

Aggregation – тук имаме отношение на отделни елементи, които в случая са част от дадена цялостна система, но също така могат и да съществуват свободно и извън нея (системата). Добър пример – птица и ято. Ятото се състои от птици, но отделните птици са си птици и без ятото. Или държави, участващи в дадена международна организация като например Европейски Съюз. Всяка може да съществъва сама по себе си.

Има и разновидност на aggregation наречена AcquaintanceAcquaintance is a weaker relationship than aggregation and suggests much looser coupling between objects.

Composition – отношение parent – child, като например Orders – OrderItems. Децата не могат да съществуват и нямат смисъл без родителя. Изчезне ли едното, изчезва(т) и дугото(другите). Добър пример – стаи и сграда. Ако разрушим сградата, няма как да имаме нейните стаи.

Друга важна концепция е т.н. Multiplicity – концепция, която допълва семантичните зависимости между отделните обекти в дадената система, с количествени характеристики. Това означава, че можем допълнително да опишем и количествените ограничения върху дадените семантични отношения.

Литература:

https://www.uml-diagrams.org/

https://www.guru99.com/uml-relationships-with-example.html

http://10minbasics.com/uml-essentials-in-10-minutes/

Builder design pattern

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

Ако го направим с един клас PhoneClass, ще трябва на конструктора да му подадем всички възможни свойства. И съответно когато инстанцираме класа, да задаваме само нужните ни за дадената инстанция, а останалите да зададем с default стойност NULL, демек не винаги може да имаме нужда от всички, а може да са доста.
Което автоматично значи, че логиката в констуктурът се усложнява, става неелегантно, конструкторът става огромен и т.н…

Kакво можем да направим – дефинираме си един, така да се каже, общ за всички телефони клас, наречен BarePhoneBuilder, в който ще имаме всичко минимално (като методи, пропъртита и т.н.) за създаването на телефон. Но няма да създаваме обект от този клас, само ще го използваме чрез отделни негови методи и пропъртита (напр. сетъри за да му сетваме различните своиства динамично).

Идеята е да го използваме за да създаваме отделни видове телефони, имащи дадени общи, но и различни, специфични свойства, да викаме само част от методите, тези от които конкретно имаме нужда. Тук идва ролята на т.н. director (или manager) – това е друг клас, който задава какво конкретно трябва да притежава даденият конкретен модел телефон, извиквайки методи и пропъртита на гореспоменатият BarePhoneBuilder.
Тоест, director-ът е на ниво конкретен телефон.

Например, направи ми Galaxy A5!
Значи ще имаме клас GalaxyA5, който ще вика методи от BarePhoneBuilder, които ни трябват в този случай, само който му трябват за да създаде даденият конкретен модел Galaxy A5.

Тези методи ще са setters като им се подава конкретна за даденият модел стойност, те я сетват за дадения обект, може и допълнителна логика да извършват, и накрая връщат $this, за да можем за по-голямо удобство, да „чейнем на влакче“ отделните методи.

И воала – имаме Glaxy A5.

Искаме Nokia FS-12 – пак използваме билдъра BarePhoneBuilder, като „на влакче“ викаме отделните му методи, които ни трябват в този случай, сетваме му отделните им стойности и… имаме Nokia FS-12.

Така вече се вижда цялата структура на Builder design pattern – имаме Билдър клас (BarePhoneBuilder), който ползваме като общ за създаване изобщо на телефони.

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

Директорът е на ниво конкретен обект, Билдърът – задава логиката, обща за всички обекти, като Директорът само му подава конкретните стойности.
Демек, „общата“ логика за създаване на конкретният обект е в Билдъра, Директорът само взема един „гол“ обект на Билдъра и и задава останалите свойства. Логиката, която е в Директора е по-скоро за неща като кога кое да бъде зададено, например ако искаме да имаме bluetooth support… и т.н….
Но как конкретно се прави „телефонът“ – в Билдъра.

Също, да не забравяме, че отделните пропърти стойности, които подаваме нa сетърите, могат и да са обекти, така идва и допълнителното определение за Builder design pattern – начин за гъвкаво създаване на обекти, съставени от други обекти (composition).

Kаква е задачата на директорите – да капсулират създаването на отделните конкретни обекти, за да става по-автоматизирано, и също – да скрият създаването от външният потребител.
Искаш Galaxy A5? Инстанцирай класът GalaxyA5 и готово, той си знае работата какво да сетне, какво да има това галакси като параметри и т.н… подробностите не те интересуват.

Естествено, за да можеш да „чейнваш на влакче“, първо трябва да имаш какво да чейнваш. Затова първо създаваш съвсем „гол“ обект, който да е „локомотивът“ и така…

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

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

Или обектите, които подаваме като аргументи на отделните сетъри, да екстендават общ абсклас или да имплементират общ интерфейс…

Един добър пример – с роботи:

<?php
/**
 * Задаваме какво задължително всеки робот трябва да има. Може с интерфейс,
 * може с абсклас.
 * Typically there's an abstract Builder class that defines an operation for each
 * component that a director may ask it to create. The operations do nothing by default.
 * A ConcreteBuilder (при нас - BareRobotBuilder) class overrides operations for
 * components it's interested in creating.
 */
interface BareRobotBuilderInterface
{
    public function setRobotHead(string $head) : void;
    public function setRobotTorso(string $torso) : void;
    public function setRobotArms(string $arms) : void;
    public function setRobotLegs(string $legs) : void;
}

/**
 * ...и го дефинираме, всъщност дефинираме методите,
 * които после Директорът ще използва за да създаде конкретен модел робот.
 */
class BareRobotBuilder implements BareRobotBuilderInterface
{
    private string $robotHead, $robotTorso, $robotArms, $robotLegs;

    public function setRobotHead(string $head) : void
    {
        $this->robotHead = $head;
    }

    public function setRobotTorso(string $torso) : void
    {
        $this->robotTorso = $torso;
    }

    public function setRobotArms(string $arms) : void
    {
        $this->robotArms = $arms;
    }

    public function setRobotLegs(string $legs) : void
    {
        $this->robotLegs = $legs;
    }
}

/**
 * Сега нека си зададем какво всеки Директор, който ще произвежда роботи,
 * трябва да може да прави.
 * Понеже идеята е да имаме много модели роботи, нека зададем общ интерфейс.
 */
interface RobotDirectorInterface
{
    public function buildRobotHead() : self;
    public function buildRobotTorso() : self;
    public function buildRobotArms() : self;
    public function buildRobotLegs() : self;
    public function getRobot() : BareRobotBuilderInterface;
}

/**
 * И да създадем един "директор", който ще задава как точно се прави
 * конкеретен модел роботи.
 */
class T1000RobotDirector implements RobotDirectorInterface
{
    public function __construct(
        private BareRobotBuilderInterface $robot
    ) { }

    public function buildRobotHead() : self
    {
        // щом имаме създаден/инджектнат "гол" робот, 
        // започваме да му дефинираме отделните части...
        $this->robot->setRobotHead('Някаква глава от течен метал бла бла…');
        return $this;
    }

    public function buildRobotTorso() : self
    {
        $this->robot->setRobotTorso('Някакво тяло от течен метал бла бла…');
        return $this;
    }

    public function buildRobotArms() : self
    {
        $this->robot->setRobotArms('Някакви ръце от течен метал бла бла…');
        return $this;
    }

    public function buildRobotLegs() : self
    {
        $this->robot->setRobotLegs('Някакви крака от течен метал бла бла…');
        return $this;
    }

    public function getRobot() : BareRobotBuilderInterface
    {
        return $this->robot;
    }
}

$t1000 = (new T1000RobotDirector(new BareRobotBuilder))
  ->buildRobotHead()  // може и да подаваме различните свойства на T-1000 с аргументи
  ->buildRobotTorso() // вместо да са хардкоднати в класа…
  //->buildRobotArms()
  ->buildRobotLegs();
var_dump($t1000->getRobot());

След като вече едно по едно сме задали отделните свойства на нашият робот, единият начин да го получим целият готов е да използваме директно getRobot() на T1000RobotDirector и от там да го върнем.
Нещо такова:

$t1000 = (new T1000RobotDirector(new BareRobotBuilder))
  ->buildRobotHead()  // може и да подаваме различните свойства на T-1000 с аргументи
  ->buildRobotTorso() // вместо да са хардкоднати в класа…
  //->buildRobotArms()
  ->buildRobotLegs();
var_dump($t1000->getRobot());

BareRobotBuilder е реално някакъв общ, абстрактен клас, където ще са само общите за всички роботи неща.
Вместо и така както е – интерфейс + клас, може и да е абстрактен клас например.

Излиза че самото създаване на конкретен робот става в T1000RobotDirector,
и то не точно крайното създаване, а само създаването на отделните части,
без самото сглобяване на целия робот.
Демек, само задава каква да е отделната част – това ще е такова, онова ще е онакова…

Builders construct their products in step-by-step fashion. Therefore the Builder class interface must be general enough to allow the construction of products for all kinds of concrete builders.
Демек Билдър интерфейса/абскласа трябва да е максимално универсален, но Директора вече решава дали да използва всичките му методи.

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

Builder design pattern напомня на Abstract factory design pattern но с разликата, че Builder създава обекта си „стъпка по стъпка“ и го връща готов в последната стъпка, а Abstract factory – наведнъж.

It gives you finer control over the construction process. Unlike creational
patterns that construct products in one shot, the Builder pattern constructs the
product step by step under the director’s control. Only when the product is
finished does the director retrieve it from the builder. Hence the Builder
interface reflects the process of constructing the product more than other
creational patterns. This gives you finer control over the construction process
and consequently the internal structure of the resulting product.

Демек, при Builder, създаването на обект е процес, не моментно събитие, което дава повече гъвкавост. Например може преди да добавиш свойство към BareRobotBuilder да искаш да провериш дали не е добавено вече, или дали не трябва нещо друго да е добавено…

Eто най-просто как стъпка по стъпка протича целият процес:
1) The client creates the Director object and configures it with the desired Builder
object.
2) Director notifies the builder whenever a part of the product should be built.
3) Builder handles requests from the director and adds parts to the product.
4) The client retrieves the product from the builder.

Времето тече „от горе надолу“

Prototype design patern

Каква е най-просто идеята?
Идеята е не да СЪЗДАВАМЕ нови инстанции на клас/класове, а да клонираме вече съществуващи ГОТОВИ обекти от съответните класове.

Например ако имаме обекти от даден клас, които са доста близки и се различават само например по едно, две неща, защо да правим нови инстанции на даденият клас? Клонираш обекта и задаваш само различните неща…

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

Как да постигнем това?
Първо, забраняваме __clone() метода на класа/класовете, които ще ползваме като прототипи.

Защо?
За да не позволяваме директно клониране на обектите по принцип, за да си напишем свой custom клонировач, който да клонира.

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

Но защо непременно интерфейс? Moже и абсклас, ако искаме да изнесем и капсулираме клонирането да е общо за всички клонирани, демек да става по един и същ начин.

Кои са „участниците“ в Prototype design pattern?

  1. Всички класове, които ще могат да бъдат клонирани трябва да имплементират общ интерфейс, който ще наречем Prototype.
  2. Те (отделните класове, които ще клонираме) ще наричаме ConcretePrototype, и те по задължение от горният интерфейс, ще имат метод който ще извършва самото клониране, например cloneMe() (демек, implements an operation for cloning itself)
  3. И разбира се Client, който ще изисква и използва тези клонинги, демек by asking a prototype to clone itself.

Демек, Клиентът казва на Конкретният прототип „клонирай се и ми дай клонинга си“, като му вика cloneMe()

Delegation vs. Forwarding in OOP

Най-просто идеята и на двете е, когато извикаме метод на един клас, той от своя страна да извика метод от друг клас, който реално да свърши работата. Разликата е как става това.

class RealPrinter  // the "receiver"
{
    public function print(): void
    {
        echo "Hello world!\n";
    }
}

class Printer  // the "sender"
{
    private RealPrinter $p;

    public function __construct()
    {
        $this->p = new RealPrinter();  // create the receiver 
    }

    public function print(): void
    {
        $this->p->print();  // calls the receiver
    }
}

// to the outside world it looks like Printer actually prints
$printer = new Printer();
$printer->print();

Горният пример, (макар и нарушаващ Inversion of Control), е пример за Delegation, защото, когато искаме от обект на Printer да свърши нещо, той създава друг обект, чиито метод върши исканото действие и тогава го връща сякаш той го е свършил.
Викащият няма как да знае кой реално е свършил работата.

Но по-същественото е, че двата класа реално погледнато нямат никакво отношение по между си, освен, че единият създава обект от другия (или му го инджектват отвън) за да го използва за да му свърши работата.
Printer не е наследник на RealPrinter, нито обратното, те не се знаят един друг.

Ако имахме наследяване, щяхме да имаме Forwarding, защото и без да създаваме специално друг обект, на който да делигираме да свърши работата, вече ще имаме такъв – аналогичният метод от родителският клас, освен разбира се ако не е private.

Затова още се казва, че класът Printer е „wrapper“, a RealPrinter е „wrapee“, тоест eдиният е „опаковката“, другият е „съдържанието“.

Composition over Inheritance

Това е начин за цялостна организация на отделните класове в ООП, според който класовете не трябва да се наследяват един друг и така да постигат code reuse, полиморфизъм…, а да се съдържат един в друг, тоест, като даден клас съдържа като пропъртита – обекти от други класове.

Едно от предимствата на този модел е, че системата става по-гъвкава, може да сдържа по-разнородни елементи, за разлика от Inheritance моделът, при който системата от класове напомня повече на „родословно дърво“.

It is more natural to build business-domain classes out of various components than trying to find commonality between them and creating a family tree.

Да си спомним от реалният живот, как например обектът от клас Car, може да съдържа много обекти от различни класове като Tyres, Engine, Brakes… които няма нужда да наследяват Car.

IS-A relationship a.k.a. Subtyping

IS-A отношение между отделни класове имаме когато имаме наследяване на клас от друг клас. Dog IS-A Animal…

Ако имаме клас А, който бива наследен от подклас B, по този начин допълвайки А, тогава можем да кажем, че имаме отношение B is-a A.

Важно е IS-A да не се бърка с HAS-A, което бихме имаме когато B е част от А като например пропърти на А.

Например, ако имаме клас Животно, наследен от клас Котка, това е IS-A.

Aко класът Котка има пропърти $food = new Whiskas() това е HAS-A.

Литература

https://www.php.net/manual/en/function.is-a.php

Closures are a poor man’s object

Има се предвид, че клоужърите и ламбдите имайки свой скоуп на променливите, могат да извършват т.н. „data hiding“, тоест да „скриват“ и капсулират информацията от околният свят в себе си.

Aко погледнем следният пример, ще видим че даденият клоужър bloa() може да има свои, частни променливи, които не са достъпни отвън.

<?php
$patapan = function(string $s): string {
    return strtoupper($s);
};

$bloa = function($a) use ($patapan): string {
    $prom1 = 'Super!';
    return $patapan($a);
};

$strings = ["apple", "orange", "banana", "coconut"];
$upprs = array_map($bloa, $strings);

var_dump($upprs);  // Dumps the uppercased array
var_dump($prom1);  // Undefined variable $prom1 in…
echo $patapan('Good question');  // GOOD QUESTION

Какво излиза на практика? Че closures и lambdas си приличат с обектите, по това, че предлагат свой скоуп и правят data hiding. Moже би затова се казва, че са „the poor man objects“.

Multiton design pattern /Registry of Singletons/

Този Design Pattern е така да се каже разширение на Singleton Design Pattern и също е известна като „Registry of Singletons“.

В смисъл такъв, че Singleton гарантира само един обект от даденият клас, а Multiton – повече от един обект от СЪЩИЯТ клас, но съдържащи се в хеш таблица или масив на друг обект.
…the multiton pattern instead ensures a single instance per key.

Представете си го като асоциативен масив с много съставки от дадена кулинарна рецепта, но от съставка можем да имаме само по една:

static private $ingredients = null;

...

self::$ingredients['tomato'] = Ingredients.getInstance('tomato');
self::$ingredients['pepper'] = Ingredients.getInstance('pepper');
self::$ingredients['cheese'] = Ingredients.getInstance('cheese');

...

и разбира се за всяка съставка, трябва първо да проверяваме дали вече не е добавена, тоест, дали вече имаме стойност в масива $shopskaSalad за даденият ключ.

...

if (self::$ingredients['tomato'] === null)
    self::$ingredients['tomato'] = Ingredients.getInstance('tomato');

if (self::$ingredients['pepper'] === null)
    self::$ingredients['pepper'] = Ingredients.getInstance('pepper');

if (self::$ingredients['cheese'] === null)
    self::$ingredients['cheese'] = Ingredients.getInstance('cheese');

...

As the name suggests, a multiton is a design pattern that helps you create multiple instances of itself. Both singleton and multiton are same, the only difference is that a multiton can store and retrieve multiple instances of itself. Obviously multiton suffers from same problems as singletons.

Here is example of multiton:

class Logger
{
    private static $instances = array();

    public static function getInstance(string $key): self
    {
        if (!array_key_exists($key, self::$instances)) {
            self::$instances[$key] = new self();
        }
        return self::$instances[$key];
    }

    // prevent creating multiple instances due to "private" constructor
    private function __construct(){}

    // prevent the instance from being cloned
    private function __clone(){}

    // prevent from being unserialized
    public function __wakeup(){}
}

$firstInstance = Logger::getInstance('file');
$secondInstance = Logger::getInstance('email');

Литература:

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

https://www.codeproject.com/Articles/1178694/Singleton-and-Multiton-Pattern

https://codeinphp.github.io/post/singleton-and-multiton-design-patterns/

Lazy loading

There are four common ways of implementing the lazy load design pattern:

  1. Lazy initialization (synonym for Lazy instantiation)
  2. Virtual proxy
  3. Ghost
  4. Value holder

1. Lazy initialization

Чак когато инстанция на даден обект ни потрябва, тогава го инстанцираме.

2. Virtual proxy

Virtual Proxy is an object with the same interface as the real object. The first time one of its methods are called it loads the real object and then delegates.

Създаваме 2 обекта, от 2 отделни класа, имплементиращи 1 общ интерфейс.
Когато извикаме някой метод от ПЪРВИЯ обект, той създава обект от ВТОРИЯ клас, присвоява го на пропърти от ПЪРВИЯТ, и ОТ ТУК НАТАТЪК каквото искаме да достъпим от ВТОРИЯ, викаме („минаваме“) през ПЪРВИЯ.

A virtual proxy object shares an interface with the „real“ object. The first time a property of the virtual proxy is accessed, the real object gets initialized. From that point on, accessing properties of the virtual object returns the corresponding property of the real object.

Явно идеята е ако оригиналният обект е ресурсоемък или времеемък, но трябва да го инициализираме по една или друга причина, да не го инициализираме него, а някакъв друг, виртуален, който има същият интерфейс, и чак когато ни трябва оригиналът, тогава във виртуалният създаваме оригиналният, и с делегиране го използваме (delegation).

3. Ghost

A „ghost“ is the object that is to be loaded in a partial state. It may only contain the object’s identifier, but it loads its own data the first time one of its properties is accessed. For example, consider that a user is about to request content via an online form. At the time of creation all we know is that content will be accessed but what action or content is unknown.

ghost is the real object without any data. The first time you call a method the ghost loads the full data into its fields.

Или с други думи, създаваме обект но съвсем опростен с идеята в последвие да добавяме към него повече данни. Добър пример би бил, ако първо създадем някакъв „полупразен“ обект, в който ще съхраняваме и обработваме информация от събмитната HTML форма, но го „пълним“ след събмитване.

$userData = array(
    "UID" = > uniqid(),
    "requestTime" => microtime(true),
    "dataType" => "",
    "request" => ""
);

if (isset($ _POST['data']) && $userData) {
    // …
}

4. Value holder

Basically, а value holder is a generic object that handles the lazy loading behavior and appears in place of the object’s data fields. When the user needs to access it, they simply ask the value holder for its value by calling the GetValue method. At that time (and only then), the value gets loaded from a database or from a service (this is not always needed).

Най просто, идеята е когато имаме нужда от дадено пропърти на даден обект, и това е свързано с ресурсоемка или времеемка операция (извличане от БД например), това извличане да стане чак тогава, през даден гетър например, в който например може да има допълнителна логика, която да запази тази информация в статично пропърти, за да не се извлича всеки път.

public function getValue($parameter) {
    if (self::$value == null) {
        self::$value = valueRetrieval($parameter);
    }
    return self::$value;
}

Литература:

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

https://en.wikipedia.org/wiki/Lazy_initialization#PHP

https://www.geeksforgeeks.org/lazy-loading-design-pattern/

Singleton design pattern

Ensure a class only has one instance, and provide a global point of access to it.

Използва се за случаите, в които искаме да гарантираме, че от даден клас ще имаме само един обект. Например при операции, които заемат повече памет, като обекти за работа с бази данни, файлове и т.н…

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

Най-просто – правим инстанцирането „отвън“ (с new …) невъзможно като направим конструктора да е private. Също и клонирането с __clone() или десериализирането с __wakeup()

Инстанцирането ще става от обикновен публичен и статичен метод, например getInstance(), който ще ни е т.н. access point.

Декларираме си статична private променлива на класа, например $inst, в която ще съхраняваме обектът от класа.

И когато извикаме getInstance() за да ни върне обект, първо проверяваме дали вече имаме стойност в $inst, тоест обект, или е NULL и ако е NULL присвояваме и обект от класа със new self(); което пък ще извика private конструктора.

Ако има обект вече в $inst – връщаме го директно, без да правим нов.

Добре е да се знае, че при Singleton Pattern деструкторът на класа също се вика автоматично или при unset() както обикновено, и трябва да е public.
Aкo не е public ще имаме грешка Call to private SomeClass::__destruct() …

Singleton design pattern е доста проста и даден не-singleton клас може лесно да се преобразува в singleton такъв само с добавяне на метод за access point демек getInstance(), едно static private пропърти и да направим конструктора и __clone да са private.

Също, на не-singleton клас можем да направим наследника да е singleton.
Както и наследникът на singleton не е автоматично singleton.

Според мен, въпреки че Singleton DP е масово считан за antipattern и трябвало било да бъде избягван на всяка цена…, аз твърдя точно обратното – Singleton е един чудесен design pattern, но просто проблемът е, че е използван грешно. Toчно простотата му му изиграва лоша шега. Не може да използваш чук за да завиеш гайка, или отвертка – за да забиеш гвоздей, и да са ти виновни съответните инструменти. Виновен си ти като „майстор“, че ги използваш грешно.

Singleton създава един напълно пълнокръвен и обикновен обект, просто следи дали вече обект от този клас не съществува. Какво лошо има в това ако например създаването на този обект е „тежко“ и/или този обект е огромен, а във същото време неговите properties са непроменливи. Например – обект с различни config settings? Защо да не използваме Singleton?

Не е нужно да си „Айнщайн“ за да разбереш, че когато на Singleton обект му промениш на едно място някое/някои пропъртита, ще важи глобално… ми нали е един обект!

Не е нужно да си „Айнщайн“ за да разбереш, че е по-добре на инджектваш обект отвън, а не да инстанцираш вътре във функцията/методът, защото така нещата стават not-testable.

Явно тези грешки се допускат масово и от там разни „айнщайновци“ носят лошото име на Singleton DP, и тези „айнщайновци“ допускат такива грешки, точно защото Singleton DP е прост и лесен.

Литература:

https://www.codeproject.com/Articles/1178694/Singleton-and-Multiton-Pattern

Abstract Factory design pattern

Или с други думи „Фабрика за фабрики“. И тази „фабрика за фабрики“ е под формата на абсклас/интерфейс, който повелява на под-фабриките си какъв тип продукти трябва да произвеждат, а не само един продукт както при Factory Method.

Представяме си един конгломерат (напр. General Motors) от много марки автомобили като Шевролет, Понтиак, Кадилак…

GM налага на отделните си марки, че всички техни коли трябва задължително да имат например 3 неща – двигател, спирачки и климатик.
Но какви конкретно – те си решават.

И ето как се оформят следната основна структура от класове, която задава същността на този design pattern – абсклас/интерфейс GM, който има 3 абсметода createEngine(), createBrakes() и createAirconditioner().
И „под-фабриките“ му – класовете Chevrolet, Pontiac и Cadillac, които трябва да ги имплементират/наследяват.

Какво трябва да има във всяка имплементация на всяка от тези под-фабрики (или concrete factory)? Да създават обекти от други класове – за различните марки двигатели, спирачки и климатици.

Да се върнем на определението: „The Abstract Factory Pattern provides an interface for creating families of related or dependent objects“.
Какви семейства или групи от свързани или зависими обекти? Ако вземем под-фабриката Chevrolet тя какво произвежда? Шевролети ще произвежда разбира се. Какво има във всеки шевролет задължително? Двигател, спирачки и климатик.

Както и Pontiac, и Cadillac… всички отделни марки, които GM произвежда (респективно наследяват/имплементират GM) също трябва да имат поне двигател, спирачки и климатик.

Тези три обекта не са ли „related“ помежду си? Са естествено.

Къде е „interface for creating…“? Kласът GM, който повелява на подфабриките си, че всяка трябва да има винаги поне трите неща.
На шевролета, понтиака или кадилака, какъв ще му е конкретно двигателят, спирачките и климатика – те вече си решават, важното е да не произведат нещо без тези трите неща.

„interface for creating families of related objects“ – това е цялата философия.

Вече отделно за всеки от трите задължителни компонента трябва да си направим отделни йерархии – например клас Engine, който го наследяват класове VolvoPenta, Trabant… клас Brakes, който го наследяват класове…., клас Airconditioner, който го наследяват Carrier, Daikin, Inventor…
Затова се казва също и „…without specifying their concrete classes…“. Демек, не казваш всяка марка какъв конкретен двигател да има, а да има двигател изобщо.

Защо трябва?

За да може в класа GM да зададем, че първият абсметод трябва да връща обект от клас Engine, а не конкретно VolvoPenta (нали казахме, че е важно да имаме двигател, без значение марката).
Съответно вторият, третият и т.н. абсметоди трябва да връщат обекти от клас Brakes и Aircondiotioners…

Разликата с Factory Method e, че там имаш един factory method, който създава един обект. Тук можеш да обединяваш различни factory methods в групи. Точно както заводът за двигатели, спирачки или климатици прави само това, а заводът за шевролети, кадилаци и понтиаци комбинира отделните техни продукти (двигатели, спирачки и климатици).

Eдно голямо предимство и на двата вида Factory Patterns – method и abstract е, че можеш да инджектваш в друг клас цялата „фабрика“ (била тя method или някоя от под-фабриките, concrete factories), вместо да инджектваш готов обект.
Демек, „на ти фабриката, направи си обекта сам“.

Затова и двете – Factory Method и Factory Abstract спадат към т.н. „creational design patterns“, защото и двете предлагат усложнен, но мощен и гъвкав начин за създаване на обекти.

Основното предимство и полза от Abstract design pattern е капсулиране на „производството“ на близки по структура обекти.

А от „близки по структура“ идва и другото му предимство – взаимозаменяемостта им.

Factory design pattern

Идеята е инстанцирането на даден клас да не става директно, а да може да се добави някаква допълнителна логика като например, кога кой клас да инстанцираме, дали да го инстанцираме изобщо и т.н…
Тоест – добавяне на логика не само в обектите, а и кога и как изобщо те да бъдат създавани (The business logic of the Creation).

Обикновено, когато ни трябва да предадем някаква капсулирана функционалност – инджектваме готов обект. Да, но може да има случаи, в които не искаме да предаваме готов обект, а името на класа, и да си го инстанцираме по наш, специфичен за нуждите начин.

И ако това въпросно, нужно нам инстанциране, е по-сложно от просто едно new(‘SomeClass’), и искаме също да го организираме по-елегантно откъм OOP, тогава можем да използваме Factory Method или Abstract Factory. Например когато искаме по-лесно да заменяме отделните „фабрики“, правим „сестрински фабрики“ на един абсклас/интерфейс.

abstract class ShapeFabric
{
    // You pass the name of the class, and possibly - arguments...
    public static function getInstance(string $cName, array $args = null): self
    {
        return new $cName($args);
    }
    abstract public function draw(): void;
    abstract public function area(): float;
}

class Circle extends ShapeFabric
{
    protected function __construct(array $args = null)
    {
        echo "Circle created \n";
    }

    public function draw(): void
    {
        echo "Circle drawed \n";
    }

    public function area(): float
    {
        return 123.123;
    }
}

class Rectangle extends ShapeFabric
{
    protected function __construct(array $args = null)
    {
        echo "Rectangle created \n";
    }

    public function draw(): void
    {
        echo "Rectangle drawed \n";
    }

    public function area(): float
    {
        return 234.234;
    }
}

class Triangle extends ShapeFabric
{
    protected function __construct(array $args = null)
    {
        echo "Triangle created \n";
    }

    public function draw(): void
    {
        echo "Triangle drawed \n";
    }

    public function area(): float
    {
        return 345.345;
    }
}

$c = ShapeFabric::getInstance('Circle');
$r = ShapeFabric::getInstance('Rectangle');
echo $r->area();

Виждаме как само с по един ред като например ShapeFabric::getInstance(‘Circle’); или ShapeFabric::getInstance(‘Rectangle’);
можем да създаваме отделни подобни обекти, които си идват с всичко, напълно пълнокръвни обекти.

Защо?
Защото си имаме обектите, които „фабриката да произвежда“, отделно са и подобни (с обща струткура), и само с това кой стринг като име на клас подадем – викаме фабриката като и подаваме съответният стринг за име на клас.

Eстествено, вече се надига въпросът: „ОК, но не може ли просто да си инстанцираме каквото ни трябва с един new Cicrle() или new Rectangle()…“.

Moже естествено, това тук е супер опростен пример.

Но ако искаме още логика в getInstance() например?

Например – някакви проверки дали можем или трябва да създаваме такъв и онакъв обект…?

Или например, нещо подобно на Singleton – да видим дали имаме вече такъв обект създаде и ако да – него да върнем, а да не правим нов?

Или ако искаме да броим от всеки клас по колко обекта вече имаме, и за всеки да имаме лимит?

Или ако искаме да отделим и капсулираме обща за създаването на отделните обекти логика?

Защо трябва да разсъждаваме опростенчески, че създаването на обект е просто да имаме едно new SomeClass() и готово?

Има три типа Factory Design Patterns:

  1. Simple Factory Pattern – това не го смятат даже да Д.П., защото е твърде просто – подаваш име на клас на някоя функция, която играе ролята на „фабрика“, евентуално има някаква логика около самото инстанциране с new, и връщаш обекта.
  2. Factory Method Pattern – тук самата „фабрика“ не е просто една функция както при Simple Factory Pattern, a структура от „фабрика“ и „обекти за произвеждане“, с общ абсклас/интерфейс, организирани за по-лесна взаимозаменяемост (виж примерът по-горе).
  3. Abstract Factory Pattern – идеята е да се зададе обща структура, която отделните наследяващи фабрики да следват. Демек, имаме повече от една фабрика, но с обща структура.

Eдно голямо предимство и на двата вида Factory Patterns – method и abstract е, че можеш да инджектваш в друг клас, условно наричан „фабрика“ името на класа, който искаш да „изфабрикуваш“, и евентуално аргументи, вместо да инджектваш готов обект.
Демек, казваш на „фабриката“, „на ти името на класа и евентуално аргументи, направи си обекта сам“.

Затова и двете – Factory Method и Factory Abstract спадат към т.н. „creational design patterns“, защото и двете предлагат усложнен, но мощен и гъвкав начин за създаване на обекти.

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

Така че виждаме, че условно можем да разделим Factory Method (но то важи и за Abstract Factory) на две части – едната е самата „фабрика“, другата част е това, което ще произвеждаме, където също трябва да имаме обща структура на продуктите.

Primitive Obsession code smell

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

Имаме клас User в който подаваме дадено пропърти, например $url като стринг и също, преди да го сетнем, правим някаква примерна валидация.

class User
{
    private string $url;

    public function setUserUrl(string $url) : void
    {
        if (false === filter_var($url, FILTER_VALIDATE_URL)) {
            throw new SomeValidationException('Wrong url bla bla bla...');
        }
        $this->url = $url;
    }

    public function getUserUrl() : string
    {
        return $this->url;
    }

    // ...
}

На пръв поглед всичко е доста просто.

Но първото, което не е ОК, е че валидацията на URL-a e в класа User. Не нарушаваме ли Sigle Responsibility SOLID принципът?

Също, ако трябва да подадем URL и на друг клас, там също трябва да я има тази валидация – повтаряне на код.

Да не говорим, че ако подаваме така, по отделно, може да имаме „експлозия от входни параметри“ ако се наложи да подаваме и други стойности.

Няма ли да е по-елегантно и гъвкаво ако създадем нов клас Url и там да изнесем валидирането и евентуално друга логика, свързана с Url-и. И където ни трябва, просто да инджектваме обект от класа Url?
T.н. „Replace Data Value with Object “ техника за рефакториране.

class User
{
    private string $url;

    public function setUserUrl(Url $url) : void
    {
        $this->url = $url;
    }

    public function getUserUrl() : string
    {
        return $this->url->getUrlAddress();
    }

    // ...
}

class Url
{
    private string $url;

    public function __construct(string $url)
    {
        if (false === filter_var($url, FILTER_VALIDATE_URL)) {
            throw new SomeValidationException('Wrong url bla bla bla...');
        }
        $this->url = $url;
    }

    public function getUrlAddress() : string
    {
        return $this->url;
    }

    // ...
}

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

Добър пример би бил ако например искаме да имаме пропърти $age за даден User, но също и трябва да я използваме тази възраст само за живи хора, а не и за например исторически личности, за които възрастта може да е стотици години.
Тогава не може просто да подаваме произволен, положителен integer.

Литература:

https://dzone.com/articles/code-quality-fighting-primitive-obsession-code-sme-1

Information hiding

Какво да крием?
Пропъртита на класа (данните на обекта).

Как да ги скрием?
Като ги зададем с видимост private или protected и ги достъпваме през сетъри и гетъри.

Защо да ги крием?
За да предотвратим възможността от това да бъдат директно и безконтролно достъпвани. Затова са и гореспоменатите сетъри и гетъри – за да зададем допълнителна логика за достъпване на въпросните пропъртита (данни на класа).

Литература:

Aspect-oriented programming

Най-просто – когато имаме допълнителни (така да се каже „сервизни“), класове, които не са част от самият бизнес модел, но са необходими (като Logger, Auth…), вместо да ги инджектваме в съответният съществен клас (напр. News) и така да го натоварваме, и него, и другите съществените (бизнес модела) с още код, правим допълнителни Aspect класове като напр. AspectLogger, който ще прави логването като Logger, и допълнително AspectConfigurator, където задаваме кой „аспект клас“ къде да бъде викан.

Ето един супер прост пример – на този клас искаме да му добавим например проверка дали даденият, логнат потребител има право да го използва.

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

<?php
class MyServices
{
    public function doAdminStuff1() {
        //some stuff only the admin should do
        echo "Calling doAdminStuff1";
    }

    public function doAdminStuff2() {
        //some stuff only the admin should do
        echo "Calling doAdminStuff2";
     }
}

Няма ли това да усложни нещата? Ами утре ако трябва и друга функционалност да се използва вътре в MyServices? Пак инджектвай, пак усложнявай методите на MyServices…? И то с какво? С нещо, което няма пряка връзка с MyServices.

Или да изнесем например Auth функционалността извън MyServices? ОК, но това ще значи навсякъде, където се викат методи на MyServices, да използваме Auth и другите (Logger…).

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

AOP is a PECL extension that enables you to use Aspect Oriented Programming in PHP. Като гледам, тук най-просто се дефинират нещо като евент хендлъри, където се задава за кой клас, кой метод, и кога да се извика какво.

<?php
aop_add_before('MyServices->doAdminStuff1()', 'theNameOfTheAuthCheckMetod');

In computingaspect-oriented software development (AOSD) is a software development technology that seeks new modularizations of software systems in order to isolate secondary or supporting functions from the main program’s business logic.
It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a „pointcut“ specification.

Core concerns – неща, директно свързани с бизнес модела.

Cross-cutting concern – така да се каже „помощни неща“ като например логване на грешки, аутентикация… Именно това е целта и идеята на Aspect Orienter Programming – да капсулира тези cross cutting concerns в т.н. „aspects“ и да направи използването им възможно, с цел да се олекоти core concern. Aspect-oriented programming aims to encapsulate cross-cutting concerns into aspects to retain modularity.

For instance, if writing an application for handling medical records, the indexing of such records is a core concern, while logging a history of changes to the record database or user database, or an authentication system, would be cross-cutting concerns since they interact with more parts of the program.

Aspects – част от целият софтуер, който може да се използва на различни места, но не е директно свързан с „program’s primary function“.
Но ако гледаме по-философски на нещата, дори и самата бизнес логика (the core concern) може да се разглежда като aspect, and by weaving them together (a process also called composition), one finally produces a whole out of the separate aspects.

Литература

https://en.wikipedia.org/wiki/Aspect-oriented_programming

https://aop-php.github.io/

https://en.wikipedia.org/wiki/Aspect-oriented_software_development