Направо към съдържанието

SOLID (обектно ориентирано програмиране)

от Уикипедия, свободната енциклопедия

В света на компютърното програмиране SOLID (Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion) е мнемоничен акроним представен от Michael Feathers за „първите пет принципа“ основани и дефинирани от Роберт Мартин – Robert C. Martin[1][2] в началото на 2000-ната година,[3] който стои зад петте основни принципа на обектно ориентираното програмиране. Когато принципите се прилагат заедно при разработването на една система, програмиста създава програма, която е лесна за поддръжка и разширение с течение на времето.[3] Принципите на SOLID са насоки, които могат да се прилагат по време на работа на софтуера за отстраняване на т.нар. „миризми по кода“ (код който не е написан качествено) от страна на програмиста при преработване на софтуерен код с цел той да е четим и разширяем. Всичко това е част от стратегията за това, че изходен софтуерен код е гъвкав (англ. agile).

Инициали Акроними Концепция
S SRP[4] Single Responsibility Principle
Принцип за единствена отговорност
Класът трябва да има една-единствена отговорност (т.е. само промени в софтуерните спецификации могат да предизвикат промяна в спецификациите на класа)
O OCP[5] Open-Closed Principle
Принцип отворен/затворен
„Софтуерните единици трябва да са отворени за разширение, но затворени за промяна.“
L LSP[6] Liskov Substitution Principle
Принцип на заместване на Лисков
Всеки наследник (подтип) трябва лесно да заменя всичките си базови типове.
I ISP[7] Interface Segregation Principle
Принцип за разделяне на интерфейсите
„Много на брой малки интерфейси е по-добре от един голям общ интерфейс.“[8]
D DIP[9] Dependency Inversion Principle
Принцип на обръщане на зависимостите
Всички класове трябва да зависят от абстракции и нито един не трябва да зависи от конкретен клас.”[8]

Принцип за единствена отговорност – SRP

[редактиране | редактиране на кода]

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

По този начин кода става модуларен разпръснат на малки парчета и всяко парче върши строго специфицирана дейност. Изключение от този принцип прави embedded development, но там не се спазва този принцип поради ограничение на паметта. „Когато има нещо за промяна в един код не трябва да имаме повече от една причина да го променяме.“ – цитат от Роберт Мартин известен сред IТ средите като „Uncle Bob“.

SRP се базира на принципите

[редактиране | редактиране на кода]
  • Strong Cohesion – силна сплотеност на функционалността на един клас, т.е. всички методи в него трябва да имат обща насока.
  • Loose coupling – класа да е максимално „разкачен“ от всички останали (хипотетично, ако се изтрие един клас, проекта да може да се билдне отново). В реалността няма как да се случи, но идеята е да се стремим по възможност да избягваме зависимости между класовете.

Принцип отворен/затворен – OCP

[редактиране | редактиране на кода]

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

Ако е спазен този принцип и ако ни е нужно ново поведение, което трябва да надгради върху стара функционалност не трябва да става чрез директна модификация на стария код. Съгласно този принцип трябва да има начин да се надгражда старата функционалност и да се разширява понеже много често този код е компилиран, т.е. е писан от други програмисти (пример една библиотека като log4net, Entity Framework и други).

Пример: в .Net на Microsoft посредством extension методите ни се дава възможност да разширяваме написаните вече и работещи функционалности въпреки че кода е компилиран.

Начини за постигане на OCP

[редактиране | редактиране на кода]
  • Чрез наследяване. – Определен метод се прави виртуален, така че който иска да разширява този метод наследява класа и пренаписва новата желана функционалност.
  • Чрез параметризация. – В процедурните езици, където няма класове има определени модули, методи приемащи определени параметри спрямо тях извършват определена функционалност. Т.е. ако променим параметрите, които подаваме можем да си разширим неговата функционалност. Пример в JavaScript, където можем да подаваме функция като параметър. В C# това става чрез делегати, но по-правилният начин да се работи с абстракция или чрез наследяване да се пренапише поведение на родителския клас.

Принцип на заместване на Лисков – LSP

[редактиране | редактиране на кода]

Възможността за заместване е принцип в обектно ориентираното програмиране. То гласи, че в компютърната програма, ако S е подтип на T, то обектите от тип T могат да бъдат заменени от тези от тип S, без това да намалява функционалностите на програмата. По-формално, Принципът за заместване на Лисков, (Liskov substitution principle LSP) е конкретно определение на връзките между субтиповете в дадена програма, наречено строго поведение на субтиповете. Въведен е от Барбара Лисков през 1987 г.

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

Проблеми при неспазването на Лисков принципа

[редактиране | редактиране на кода]
  • Нарушаваме полиморфизма. – Когато класът наследник не наследява всички функционалности на класа родител.
  • „Поправяне“ чрез type-checking. – Много грешен похват, който чупи принципите на ООП. Ако някога стигнете до момент, в който ви се прииска да го ползвате, замислете се, дали наистина това наистина е нужно, или просто трябва да овъррайднете методите в класовете наследници.

Класически нарушения на Лисков принципа

[редактиране | редактиране на кода]
  • „Type checking“ за различните методи.
  • Овърритън методи, които не са имплементирани.
  • Виртуални методи в конструктора.

Пример за нарушаване на Лисков принципа

[редактиране | редактиране на кода]

Типичен пример за нарушаване на Лисков принципа е, когато имате клас „Квадрат“, който наследява клас „Правоъгълник“. Квадратът в математиката е вид правоъгълник, но в програмирането нещата не стоят точно така. Ако във вашата програма имате квадрат, който наследява правоъгълник, това ще бъде некоректно, защото квадратът ще наследи променливите за ширина и височина на правоъгълника, а всички знаем, че на квадрата всички страни са равни.

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

Принцип за разделяне на интерфейсите – ISP

[редактиране | редактиране на кода]

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

„Дебел“, голям, пълен с различни методи интерфейс води до:

  • Класовете да имплементират методи, които не са им нужни.
  • Увеличена свързаност(coupling) между класовете.
  • Намалена гъвкавост.
  • Поддръжката става по-трудна.

Принцип на обръщане на зависимостите – DIP

[редактиране | редактиране на кода]

Принципът на обръщане на зависимостите е специфичен начин за отвързване (отделяне; decoupling) на софтуерните модули. Когато следваме този принцип, модулите на по-високо ниво не зависят от тези на по-ниско ниво, като и двата трябва да зависят само и единствено от абстракции. В същото време абстракциите не трябва да зависят от детайлите, а детайлите трябва да зависят от абстракциите. Този принцип е въведен от Роберт Мартин.

Зависимост наричаме даден клас или модул, от който зависи друг клас или модул.

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

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

Правила за обръщане на зависимостите

[редактиране | редактиране на кода]
  • Класовете трябва да декларират от какво имат нужда.
  • Скритите зависимости трябва да бъдат показани.
  • Зависимостите трябва да са абстракции.

Начини за обръщане на зависимостите

[редактиране | редактиране на кода]
  • През конструктора Този начин е добър, защото за да създадем инстанция на даден обект, ние ще трябва да подадем през конструктора всичките зависимости от които се нуждае обекта, и ако съответно пропуснем да подадем някой, няма да можем да създадем желаният обект. По този начин класът документира в конструктора си от какво се нуждае за да работи и няма скрити зависимости. Друг плюс е, че класът винаги ще бъде във валидно състояние, защото няма как да не подадеш някоя зависимост в конструктора и да създадеш обекта без нея. Недостатъците на този метод са: Това води до въвеждането на много параметри, когато се инстанцира съответния клас. Някои от методите няма да се нуждаят от абсолютно всички параметри подадени в конструктора.
  • През пропъртитата Предимствата на този метод са, че можем да променяме състоянието на зависимостите, когато пожелаем, което прави този метод много гъвкав. Недостатъците са, че ако не сетнем пропъртито, то ще остане със стойност null, и съответната инстанция на клас няма да бъде валидна.
  • През параметър на метод Ако зависимостта се ползва само в текущия метод, този начин е подходящ, но ако методите са повече е добре зависимостта да се подаде чрез конструктора. Недостатъка е, че когато подаваме много параметри на метода, чупим неговата сигнатура.

Класически нарушения на принципа

[редактиране | редактиране на кода]
  • Използване на ключовата дума „new“. Когато създавате нови инстанции на класовете, които са ви нужни, а не ги приемате както в посочените горе случаи, вие се обвързвате максимално с класовете, а не с техните абстракции (интерфейси, абстрактни класове).
  • Използване на статични класове. Статичния клас не може да има абстракция над себе си (интерфейс), което означава, че не може да имате обръщане на зависимостите и статичен клас на едно място.
  1. „Principles Of OOD“, Robert C. Martin („Uncle BOB“), butunclebob.com, Last verified 2014-07-17.
  2. „Getting a SOLID start.“
  3. а б „SOLID Object-Oriented Design“ Архив на оригинала от 2014-03-29 в Wayback Machine., Sandi Metz (Duke University), Talk given at the 2009 Gotham Ruby Conference in May, 2009.
  4. „Single Responsibility Principle“ (PDF).
  5. „Open/Closed Principle“ (PDF).
  6. „Liskov Substitution Principle“ (PDF).
  7. „Interface Segregation Principle“ (PDF).
  8. а б „Design Principles and Design Patterns“, Robert C. Martin („Uncle Bob“), objectmentor.com.
  9. „Dependency Inversion Principle“ (PDF).
  Тази страница частично или изцяло представлява превод на страницата SOLID (object-oriented design) в Уикипедия на английски. Оригиналният текст, както и този превод, са защитени от Лиценза „Криейтив Комънс – Признание – Споделяне на споделеното“, а за съдържание, създадено преди юни 2009 година – от Лиценза за свободна документация на ГНУ. Прегледайте историята на редакциите на оригиналната страница, както и на преводната страница, за да видите списъка на съавторите. ​

ВАЖНО: Този шаблон се отнася единствено до авторските права върху съдържанието на статията. Добавянето му не отменя изискването да се посочват конкретни източници на твърденията, които да бъдат благонадеждни.​