├── README.md ├── images ├── The Dependency Inversion Principle.png ├── The Interface Segregation Principle.png ├── The Liskov Substitution Principle.png ├── The Open Closed Principle.png └── The Single Responsibility Principle.png ├── with-solid ├── The Dependency Inversion Principle.php ├── The Interface Segregation Principle.php ├── The Liskov Substitution Principle.php ├── The Open Closed Principle.php └── The Single Responsibility Principle.php └── without-solid ├── The Dependency Inversion Principle.php ├── The Interface Segregation Principle.php ├── The Liskov Substitution Principle.php ├── The Open Closed Principle.php └── The Single Responsibility Principle.php /README.md: -------------------------------------------------------------------------------- 1 | # SOLID Principles 2 | SOLID — мнемонический акроним, введённый Майклом Фэзерсом (Michael Feathers) для первых пяти принципов, названных Робертом Мартином в начале 2000-х, которые означали пять основных принципов объектно-ориентированного программирования и проектирования. 3 | 4 | ## Для чего нужны принципы SOLID? ## 5 | Принципы SOLID — это набор правил, которые необходимо применять во время работы над программным обеспечением(ПО) для его улучшения. 6 | 7 | Удивительно то, что принципы были сформулированы несколько десятков лет назад и до сих пор актуальны. Это может говорить только о их еффективности. 8 | 9 | ## S – The Single Responsibility Principle (Принцип единственной ответственности) ## 10 | Каждый класс выполняет лишь одну задачу. 11 | 12 | ![The Single Responsibility Principle](images/The%20Single%20Responsibility%20Principle.png) 13 | 14 | Очень простой, но в тоже время очень и очень важный принцип. Необходимо следить в вашем ПО, чтобы каждый класс или интерфейс не был перегружен лишней логикой и выполнял только одну задачу. 15 | 16 | Нужно понимать, что каждое изменение в логике работы класса влечет за собой изменения в коде. Если ваш класс имеет более одной ответственности, то изменения его при изменении бизнес-логики будут происходить чаще. Так же класс взаимодействует с большим количеством других классов, что сильнее их связывает между собой и становится сложнее в поддержке. 17 | 18 | Часто при правке багов и хотфиксов нарушается этот принцип. В случае хотфиксов нужно занести это в технический долг и выполнить его в ближайшее время. 19 | 20 | ### Пример 21 | 22 | Есть класс Order в котором описана логика работы с заказом. 23 | 24 | ```php 25 | class Order { 26 | 27 | public function calculateTotalSum() {/*...*/} 28 | 29 | public function getItems() {/*...*/} 30 | 31 | public function getItemCount() {/*...*/} 32 | 33 | public function addItem( $item ) {/*...*/} 34 | 35 | public function deleteItem( $item ) {/*...*/} 36 | 37 | public function printOrder() {/*...*/} 38 | 39 | public function showOrder() {/*...*/} 40 | 41 | public function load() {/*...*/} 42 | 43 | public function save() {/*...*/} 44 | 45 | public function update() {/*...*/} 46 | 47 | public function delete() {/*...*/} 48 | 49 | } 50 | ``` 51 | 52 | Необходимо разделить его на несколько классов и выделить логику работы с базой и отображением в отдельные классы 53 | 54 | ```php 55 | class Order { 56 | 57 | public function calculateTotalSum() {/*...*/} 58 | 59 | public function getItems() {/*...*/} 60 | 61 | public function getItemCount() {/*...*/} 62 | 63 | public function addItem( $item ) {/*...*/} 64 | 65 | public function deleteItem( $item ) {/*...*/} 66 | 67 | } 68 | 69 | class OrderRepository { 70 | 71 | public function load( $orderID ) {/*...*/} 72 | 73 | public function save( $order ) {/*...*/} 74 | 75 | public function update( $order ) {/*...*/} 76 | 77 | public function delete( $order ) {/*...*/} 78 | 79 | } 80 | 81 | class OrderViewer { 82 | 83 | public function printOrder( $order ) {/*...*/} 84 | 85 | public function showOrder( $order ) {/*...*/} 86 | 87 | } 88 | ``` 89 | ## O – The Open Closed Principle (Принцип открытости/закрытости) ## 90 | 91 | Классы должны быть открыты для расширения и закрыты для модификации. 92 | 93 | ![The Open Closed Principle](images/The%20Open%20Closed%20Principle.png) 94 | В идеальном мире это для добавление нового функционала нужно добавлять новый код, а не изменять старый. 95 | 96 | Багфикс, рефакторинг и улучшение производительности это не нарушение этого принципа. Принцип гласит именно про изменение логики работы ПО. 97 | 98 | ### Пример 99 | 100 | У нас есть класс OrderRepository. В его методе load описана работа получения заказа из БД. 101 | 102 | ```php 103 | class OrderRepository { 104 | 105 | public function load( $orderID ) { 106 | $pdo = new PDO( 107 | $this->config->getDsn(), 108 | $this->config->getDBUser(), 109 | $this->config->getDBPassword() 110 | ); 111 | $statement = $pdo->prepare( "SELECT * FROM `orders` WHERE id=:id" ); 112 | $statement->execute( array( ":id" => $orderID ) ); 113 | 114 | return $query->fetchObject( "Order" ); 115 | } 116 | 117 | public function save( $order ) {/*...*/} 118 | 119 | public function update( $order ) {/*...*/} 120 | 121 | public function delete( $order ) {/*...*/} 122 | 123 | } 124 | ``` 125 | 126 | Когда появляется необходимость получать заказы не только с базы, а например с API, то можно поступить следующим образом: 127 | 1. Создать интерфейс IOrderSource 128 | 2. Сделать 2 класса MySQLOrderSource и ApiOrderSource, которые выполняют данный интерфейс 129 | 3. И передавать в конструктор класса OrderRepository инстанс, который реализует интерфейс IOrderSource. 130 | 131 | Таким образом мы можем легко добавлять новый источник заказов просто реализовав класс с интерфейсом IOrderSource. 132 | 133 | ```php 134 | interface IOrderSource { 135 | 136 | public function load( $orderID ); 137 | 138 | public function save( $order ); 139 | 140 | public function update( $order ); 141 | 142 | public function delete( $order ); 143 | 144 | } 145 | 146 | class MySQLOrderSource implements IOrderSource { 147 | 148 | public function load( $orderID ) {/*...*/} 149 | 150 | public function save( $order ) {/*...*/} 151 | 152 | public function update( $order ) {/*...*/} 153 | 154 | public function delete( $order ) {/*...*/} 155 | 156 | } 157 | 158 | class ApiOrderSource implements IOrderSource { 159 | 160 | public function load( $orderID ) {/*...*/} 161 | 162 | public function save( $order ) {/*...*/} 163 | 164 | public function update( $order ) {/*...*/} 165 | 166 | public function delete( $order ) {/*...*/} 167 | 168 | } 169 | 170 | class OrderRepository { 171 | 172 | private $source; 173 | 174 | public function __constructor( IOrderSource $source ) { 175 | $this->source = $source; 176 | } 177 | 178 | public function load( $orderID ) { 179 | return $this->source->load( $orderID ); 180 | } 181 | 182 | public function save( $order ) {/*...*/} 183 | 184 | public function update( $order ) {/*...*/} 185 | 186 | } 187 | ``` 188 | 189 | ## L – The Liskov Substitution Principle (Принцип подстановки Барбары Лисков) ## 190 | 191 | Наследники должны повторять поведение родительского класса и должны вести себя без сюрпризов. 192 | 193 | ![The Liskov Substitution Principle](images/The%20Liskov%20Substitution%20Principle.png) 194 | 195 | ### Пример ### 196 | 197 | У нас есть класс LessonRepository, который в методе getAll возвращает массив всех уроков из файла. Появилась необходимость получать уроки из БД. Создаем класс DatabaseLessonRepository, наследуем его от LessonRepository и переписываем метод getAll. 198 | 199 | ```php 200 | class LessonRepository { 201 | 202 | //return array of lesson through file system. 203 | public function getAll() { 204 | return $files; 205 | } 206 | 207 | } 208 | 209 | class DatabaseLessonRepository extends LessonRepository { 210 | 211 | //return a Collection type instead of array 212 | public function getAll() { 213 | return Lesson::all(); 214 | } 215 | 216 | } 217 | ``` 218 | 219 | В методе getAll у класса DatabaseLessonRepository вместо коллекции мы должны вернуть массив 220 | 221 | ```php 222 | interface LessonRepositoryInterface { 223 | 224 | public function getAll(): array; 225 | 226 | } 227 | 228 | class FilesystemLessonRepository implements LessonRepositoryInterface { 229 | 230 | public function getAll(): array { 231 | return $files; 232 | } 233 | 234 | } 235 | 236 | 237 | class DatabaseLessonRepository implements LessonRepositoryInterface { 238 | 239 | public function getAll(): array { 240 | return Lesson::all()->toArray(); 241 | } 242 | 243 | } 244 | ``` 245 | ## I – The Interface Segregation Principle (Принцип разделения интерфейса) ## 246 | 247 | Много мелких интерфейсов лучше, чем один большой. 248 | 249 | ![The Interface Segregation Principle](images/The%20Interface%20Segregation%20Principle.png) 250 | 251 | ### Пример ### 252 | 253 | У нас есть интерфейс Bird, который имеет методы eat и fly. Когда в коде появляется Penguin, который не умеет летать нужно бросить Exception. 254 | 255 | ```php 256 | interface Bird { 257 | 258 | public function eat(); 259 | 260 | public function fly(); 261 | 262 | } 263 | 264 | class Duck implements Bird { 265 | 266 | public function eat() {/*...*/} 267 | 268 | public function fly() {/*...*/} 269 | 270 | } 271 | 272 | class Penguin implements Bird { 273 | 274 | public function eat() {/*...*/} 275 | 276 | public function fly() {/* exception */} 277 | 278 | } 279 | ``` 280 | 281 | Вместо Exception лучше разделить интерфейсы для птицы на Bird, FlyingBird и RunningBird. 282 | 283 | ```php 284 | interface Bird { 285 | 286 | public function eat(); 287 | 288 | } 289 | 290 | interface FlyingBird { 291 | 292 | public function fly(); 293 | 294 | } 295 | 296 | interface RunningBird { 297 | 298 | public function run(); 299 | 300 | } 301 | 302 | class Duck implements Bird, FlyingBird { 303 | 304 | public function eat() {/*...*/} 305 | 306 | public function fly() {/*...*/} 307 | 308 | } 309 | 310 | class Penguin implements Bird, RunningBird { 311 | 312 | public function eat() {/*...*/} 313 | public function run() {/*...*/} 314 | 315 | } 316 | ``` 317 | 318 | ## D – The Dependency Inversion Principle (Принцип инверсии зависимостей) ## 319 | 320 | Зависимость на абстракциях, нет зависимостей на что-то конкретное. 321 | 322 | ![The Dependency Inversion Principle](images/The%20Dependency%20Inversion%20Principle.png) 323 | 324 | Самое простое решение начать применять этот принцип это писать тесты т.к. при их написании тестов необходимо мокать какие-то данные и как итог: проще переписать класс, чем написать на него тест. 325 | 326 | ### Пример ### 327 | 328 | У нас есть класс EBookReader, который принимает в коструктор объект класса PDFBook и в методе read - читает его. Когда появляется необходимость читать не только из PDF-файла класс необходимо изменять. 329 | 330 | ```php 331 | class EBookReader { 332 | 333 | private $book; 334 | 335 | public function __construct( PDFBook $book ) { 336 | $this->book = $book; 337 | } 338 | 339 | public function read() { 340 | return $this->book->read(); 341 | } 342 | 343 | } 344 | 345 | class PDFBook { 346 | 347 | public function read() { /*...*/ 348 | } 349 | 350 | } 351 | ``` 352 | 353 | Лучше сделать интерфейс EBook с методом read. И тогда в EBookReader мы сможем передавать любые объекты, которые реализовывают интерфейс EBook. 354 | 355 | ```php 356 | interface EBook { 357 | 358 | public function read(); 359 | 360 | } 361 | 362 | class EBookReader { 363 | 364 | private $book; 365 | 366 | public function __construct( EBook $book ) { 367 | $this->book = $book; 368 | } 369 | 370 | public function read() { 371 | return $this->book->read(); 372 | } 373 | 374 | } 375 | 376 | class PDFBook implements EBook { 377 | 378 | public function read() {/*...*/} 379 | 380 | } 381 | 382 | class MobiBook implements EBook { 383 | 384 | public function read() {/*...*/} 385 | 386 | } 387 | ``` 388 | 389 | ## Основные триггеры того, что принципы SOLID нарушены: ## 390 | 391 | - Оператор switch 392 | - Большое кол-во констант 393 | - new внутри методов класса 394 | - instanceof 395 | 396 | ## Часть проблем решает применение паттернов проектирования: ## 397 | 398 | Найболее полулярные паттерны в PHP: 399 | - Strategy 400 | - State 401 | - Chain of Responsibility 402 | - Visitor 403 | - Decorator 404 | - Composition 405 | - Factory -------------------------------------------------------------------------------- /images/The Dependency Inversion Principle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wppunk/SOLID/183b50d53a5e322ecedc908c42e5e09090ea18e3/images/The Dependency Inversion Principle.png -------------------------------------------------------------------------------- /images/The Interface Segregation Principle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wppunk/SOLID/183b50d53a5e322ecedc908c42e5e09090ea18e3/images/The Interface Segregation Principle.png -------------------------------------------------------------------------------- /images/The Liskov Substitution Principle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wppunk/SOLID/183b50d53a5e322ecedc908c42e5e09090ea18e3/images/The Liskov Substitution Principle.png -------------------------------------------------------------------------------- /images/The Open Closed Principle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wppunk/SOLID/183b50d53a5e322ecedc908c42e5e09090ea18e3/images/The Open Closed Principle.png -------------------------------------------------------------------------------- /images/The Single Responsibility Principle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wppunk/SOLID/183b50d53a5e322ecedc908c42e5e09090ea18e3/images/The Single Responsibility Principle.png -------------------------------------------------------------------------------- /with-solid/The Dependency Inversion Principle.php: -------------------------------------------------------------------------------- 1 | book = $book; 16 | } 17 | 18 | public function read() { 19 | return $this->book->read(); 20 | } 21 | 22 | } 23 | 24 | class PDFBook implements EBook { 25 | 26 | public function read() { /*...*/ 27 | } 28 | 29 | } 30 | 31 | class MobiBook implements EBook { 32 | 33 | public function read() { /*...*/ 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /with-solid/The Interface Segregation Principle.php: -------------------------------------------------------------------------------- 1 | toArray(); 19 | } 20 | } -------------------------------------------------------------------------------- /with-solid/The Open Closed Principle.php: -------------------------------------------------------------------------------- 1 | source = $source; 49 | } 50 | 51 | public function load( $orderID ) { 52 | 53 | return $this->source->load( $orderID ); 54 | } 55 | 56 | public function save( $order ) {/*...*/ 57 | } 58 | 59 | public function update( $order ) {/*...*/ 60 | } 61 | } -------------------------------------------------------------------------------- /with-solid/The Single Responsibility Principle.php: -------------------------------------------------------------------------------- 1 | book = $book; 12 | } 13 | 14 | public function read() { 15 | return $this->book->read(); 16 | } 17 | 18 | } 19 | 20 | class PDFBook { 21 | 22 | public function read() { /*...*/ 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /without-solid/The Interface Segregation Principle.php: -------------------------------------------------------------------------------- 1 | config->getDsn(), 10 | $this->config->getDBUser(), 11 | $this->config->getDBPassword() 12 | ); 13 | $statement = $pdo->prepare( "SELECT * FROM `orders` WHERE id=:id" ); 14 | $statement->execute( array( ":id" => $orderID ) ); 15 | 16 | return $query->fetchObject( "Order" ); 17 | } 18 | 19 | public function save( $order ) {/*...*/ 20 | } 21 | 22 | public function update( $order ) {/*...*/ 23 | } 24 | 25 | public function delete( $order ) {/*...*/ 26 | } 27 | } -------------------------------------------------------------------------------- /without-solid/The Single Responsibility Principle.php: -------------------------------------------------------------------------------- 1 |