diff --git a/resources/views/library/solid.blade.php b/resources/views/library/solid.blade.php new file mode 100644 index 00000000..9bb31163 --- /dev/null +++ b/resources/views/library/solid.blade.php @@ -0,0 +1,34 @@ +@extends('layout') +@section('title', 'SOLID принципы') +@section('description', 'Мы поочередно познакомимся с каждым принципом, чтобы понять, как SOLID может помочь вам стать лучшим разработчиком.') +@section('content') + + + Объектно-ориентированные + SOLID принципы + + Мы поочередно познакомимся с каждым принципом, чтобы понять, как SOLID может помочь вам стать лучшим разработчиком. + + +
+ +
+
+
+ + @php + $sections = collect([ + 'basics', + 'srp', + 'ocp', + 'lsp', + 'lsp', + 'dip', + ]) + ->map(fn ($file) => \Illuminate\Support\Str::of($file)->start('solid/')) + ->map(fn ($file) => new \App\Library($file)); + @endphp + + @include('particles.library-section', ['sections' => $sections]) + +@endsection diff --git a/routes/web.php b/routes/web.php index 239db118..90b11753 100644 --- a/routes/web.php +++ b/routes/web.php @@ -55,7 +55,7 @@ Route::view('/library/security', 'library.security')->name('library.security'); Route::view('/library/how-to-ask', 'library.how-to-ask')->name('library.how-to-ask'); Route::view('/library/collection', 'library.collection')->name('library.collection'); - +Route::view('/library/solid', 'library.solid')->name('library.solid'); /* |-------------------------------------------------------------------------- | Open Quiz diff --git a/storage/library/solid/basics.md b/storage/library/solid/basics.md new file mode 100644 index 00000000..718195b0 --- /dev/null +++ b/storage/library/solid/basics.md @@ -0,0 +1,15 @@ +--- +title: "Введение" +description: "SOLID - это акроним для первых пяти принципов объектно-ориентированного проектирования от Роберта Мартина." +--- + +*SOLID* - это основа объектно-ориентированного проектирования (ООП), которая помогает создавать гибкие, расширяемые и +поддерживаемые программы. Когда ты понимаешь эти принципы, ты можешь писать код, который легко изменять и дополнять. + +Эти принципы не только полезны для написания качественного кода, но и часто встречаются на собеседованиях на должности +разработчика. Рекрутеры и работодатели ценят знание SOLID, так как это указывает на твою способность создавать +высококачественное программное обеспечение. + +Понимание SOLID помогает избежать типичных проблем, таких как сложная поддержка кода, неожиданные ошибки и сложности в +расширении функциональности. Поэтому освоение этих принципов не только делает тебя лучшим разработчиком, но и помогает +тебе быть востребованным на рынке труда. diff --git a/storage/library/solid/dip.md b/storage/library/solid/dip.md new file mode 100644 index 00000000..bede2e28 --- /dev/null +++ b/storage/library/solid/dip.md @@ -0,0 +1,71 @@ +--- +title: "Принцип инверсии зависимостей" +description: "Любые более высокие (дочерние) классы всегда должны зависеть от абстракций, а не от деталей." +--- + +Принцип инверсии зависимостей (Dependency Inversion Principle, DIP) гласит, что модули высокого уровня не должны +зависеть от модулей низкого уровня, а оба типа модулей должны зависеть от абстракций. Также он утверждает, что +абстракции не должны зависеть от деталей, а детали должны зависеть от абстракций. + +Давай разберемся, что это означает на примере: + +```php +mailer = $mailer; + } +} +``` + +Здесь класс SendWelcomeMessage зависит от конкретной реализации Mailer, что нарушает принцип инверсии зависимостей. Если +мы захотим изменить способ отправки сообщений, нам придется изменять и класс SendWelcomeMessage. + +Для исправления этой проблемы мы можем использовать абстракцию в виде интерфейса Mailer: + +```php +interface Mailer +{ + public function send(); +} + +class SmtpMailer implements Mailer +{ + public function send() + { + // Реализация отправки через SMTP + } +} + +class SendGridMailer implements Mailer +{ + public function send() + { + // Реализация отправки через SendGrid + } +} + +class SendWelcomeMessage +{ + private $mailer; + + public function __construct(Mailer $mailer) + { + $this->mailer = $mailer; + } +} +``` + +Теперь класс SendWelcomeMessage зависит от абстракции Mailer, а не от конкретной реализации. Мы можем легко изменить +способ отправки сообщений, просто передавая нужную реализацию интерфейса Mailer в конструктор SendWelcomeMessage. Это +соответствует принципу инверсии зависимостей. diff --git a/storage/library/solid/isp.md b/storage/library/solid/isp.md new file mode 100644 index 00000000..a8521c29 --- /dev/null +++ b/storage/library/solid/isp.md @@ -0,0 +1,119 @@ +--- +title: "Принцип разделения интерфейса" +description: "Интерфейсов должно быть много." +--- + +Принцип разделения интерфейса (Interface Segregation Principle, ISP) предписывает, что клиенты не должны зависеть от +методов, которые они не используют. Вместо этого интерфейсы должны быть разделены на более мелкие, специализированные +интерфейсы, чтобы клиенты могли реализовывать только те методы, которые им нужны. + +Давай разберем, как это работает на примере: + +```php +// Нарушение принципа разделения интерфейса +interface Workable +{ + public function canCode(); + public function code(); + public function test(); +} + +class Programmer implements Workable +{ + public function canCode() + { + return true; + } + + public function code() + { + return 'coding'; + } + + public function test() + { + return 'testing in localhost'; + } +} + +class Tester implements Workable +{ + public function canCode() + { + return false; + } + + public function code() + { + throw new Exception('Opps! I can not code'); + } + + public function test() + { + return 'testing in test server'; + } +} + +class ProjectManagement +{ + public function processCode(Workable $member) + { + if ($member->canCode()) { + $member->code(); + } + } +} +``` + +В этом примере интерфейс Workable содержит методы canCode(), code() и test(). Проблема в том, что не все классы, +реализующие этот интерфейс, могут выполнять все эти действия. Например, класс Tester не может кодировать, но он должен +реализовать метод code(), потому что интерфейс Workable требует это. + +Чтобы исправить это, мы можем разделить интерфейс на более мелкие и специализированные интерфейсы: + +```php +// Улучшенный вариант +interface Codeable +{ + public function code(); +} + +interface Testable +{ + public function test(); +} + +class Programmer implements Codeable, Testable +{ + public function code() + { + return 'coding'; + } + + public function test() + { + return 'testing in localhost'; + } +} + +class Tester implements Testable +{ + public function test() + { + return 'testing in test server'; + } +} + +class ProjectManagement +{ + public function processCode(Codeable $member) + { + $member->code(); + } +} +``` + +Теперь интерфейсы Codeable и Testable более специализированы. Класс Programmer реализует оба интерфейса, потому что он +может кодировать и тестировать. Класс Tester реализует только интерфейс Testable, потому что он может только +тестировать. Таким образом, классы могут реализовывать только те методы, которые им нужны, что соответствует принципу +разделения интерфейса. diff --git a/storage/library/solid/lsp.md b/storage/library/solid/lsp.md new file mode 100644 index 00000000..36916699 --- /dev/null +++ b/storage/library/solid/lsp.md @@ -0,0 +1,89 @@ +--- +title: "Принцип подстановки Барбары Лисков" +description: "Дочерние классы должны работать так, что бы ими можно было заменить родительские." +--- + +Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP) утверждает, что поведение подклассов должно быть +совместимо с поведением их суперклассов. Другими словами, объекты подтипов должны быть заменяемыми экземплярами своих +супертипов без изменения ожидаемого поведения программы. Давай разберемся с примером, чтобы лучше понять этот принцип: + +```php +height = $height; + } + + public function getHeight() + { + return $this->height; + } + + public function setWidth($width) + { + $this->width = $width; + } + + public function getWidth() + { + return $this->width; + } + + public function area() + { + return $this->height * $this->width; + } +} + +class Square extends Rectangle +{ + public function setHeight($value) + { + $this->width = $value; + $this->height = $value; + } + + public function setWidth($value) + { + $this->width = $value; + $this->height = $value; + } +} + +class RectangleTest +{ + private $rectangle; + + public function __construct(Rectangle $rectangle) + { + $this->rectangle = $rectangle; + } + + public function testArea() + { + $this->rectangle->setHeight(2); + $this->rectangle->setWidth(3); + // Ожидаем, что площадь прямоугольника будет 6 + } +} +``` + +В данном примере класс Square наследуется от Rectangle, что кажется логичным, так как квадрат является частным случаем +прямоугольника. Однако, нарушается принцип подстановки Барбары Лисков из-за того, что Square переопределяет методы +setHeight() и setWidth() так, чтобы они всегда делали высоту равной ширине. + +Что делает этот пример нарушением LSP? Дело в том, что ожидается, что при вызове setHeight() и setWidth() объекта +Rectangle сначала будет изменяться одно измерение, а потом другое. Однако в случае Square эти методы нарушают это +ожидание, что может привести к непредсказуемому поведению в программах, которые используют Rectangle или его подтипы. + +Как исправить это? Один из способов - пересмотреть архитектуру классов так, чтобы Square не наследовался от Rectangle, +так как это нарушает LSP. Вместо этого, можно использовать композицию или выделить общий интерфейс для обоих классов и +разработать их независимо. diff --git a/storage/library/solid/ocp.md b/storage/library/solid/ocp.md new file mode 100644 index 00000000..369ff7df --- /dev/null +++ b/storage/library/solid/ocp.md @@ -0,0 +1,85 @@ +--- +title: "Принцип открытости/закрытости" +description: "Классы должны предоставлять интерфейсы для их использования, все остальное должно быть закрыто." +--- + +Принцип открытости/закрытости гласит, что классы должны быть открыты для расширения (путем добавления нового кода) и +закрыты для модификации (существующий код не должен изменяться). + +Принцип открытости/закрытости обеспечивает гибкость и стабильность программного кода. Суть его заключается в том, что +после того как класс написан и протестирован, его код не должен изменяться при добавлении новой функциональности. Вместо +этого, новая функциональность должна добавляться через расширение класса или использование интерфейсов. + +Давай посмотрим на пример, чтобы понять это. + +```php +class Programmer +{ + public function code() + { + return 'coding'; + } +} + +class Tester +{ + public function test() + { + return 'testing'; + } +} + +class ProjectManagement +{ + public function process($member) + { + if ($member instanceof Programmer) { + $member->code(); + } elseif ($member instanceof Tester) { + $member->test(); + }; + + throw new Exception('Invalid input member'); + } +} +``` + +Этот код нарушает принцип открытости/закрытости, потому что для добавления новых видов сотрудников (например, +дизайнеров) нам придется изменять класс ProjectManagement. + +Давай исправим это, используя интерфейсы: + +```php +interface Workable +{ + public function work(); +} + +class Programmer implements Workable +{ + public function work() + { + return 'coding'; + } +} + +class Tester implements Workable +{ + public function work() + { + return 'testing'; + } +} + +class ProjectManagement +{ + public function process(Workable $member) + { + return $member->work(); + } +} +``` + +Теперь мы используем интерфейс Workable, который гарантирует, что все классы, реализующие этот интерфейс, будут иметь +метод `work()`. Таким образом, мы можем добавлять новые типы сотрудников, реализующих интерфейс Workable, без изменения +кода класса ProjectManagement. Это соответствует принципу открытости/закрытости. diff --git a/storage/library/solid/srp.md b/storage/library/solid/srp.md new file mode 100644 index 00000000..f797bc48 --- /dev/null +++ b/storage/library/solid/srp.md @@ -0,0 +1,86 @@ +--- +title: "Принцип единственной ответственности" +description: "Класс должен решать только одну задачу (иметь одну ответственность)." +--- + +Принцип единой ответственности (SRP) гласит, что каждый класс должен заниматься только одним делом. +Вот пример, чтобы лучше понять это: + +Представь, что у тебя есть класс "Отчет" (Report), который должен предоставлять информацию о некоторых данных. Однако, в +текущем виде он не соблюдает принцип SRP, потому что помимо предоставления данных он также занимается их форматированием +в JSON. + +```php +class Report +{ + public function title(): string + { + return 'Report Title'; + } + + public function date(): string + { + return '2016-04-21'; + } + + public function contents(): array + { + return [ + 'title' => $this->title(), + 'date' => $this->date(), + ]; + } + + public function formatJson(): string + { + return json_encode($this->contents()); + } +} +``` + +Проблема здесь в том, что класс Report занимается слишком многим - он не только предоставляет данные, но и форматирует +их в JSON. Допустим, тебе потребуется отформатировать данные в HTML. В таком случае, класс Report придется изменять, +нарушая принцип SRP. + +Чтобы исправить это, мы можем разделить ответственности. Давай создадим новый класс JsonReportFormatter, который будет +отвечать только за форматирование данных в JSON: + +```php +class Report +{ + public function title(): string + { + return 'Report Title'; + } + + public function date(): string + { + return '2016-04-21'; + } + + public function contents(): array + { + return [ + 'title' => $this->title(), + 'date' => $this->date(), + ]; + } +} + +interface ReportFormattable +{ + public function format(Report $report); +} + +class JsonReportFormatter implements ReportFormattable +{ + public function format(Report $report) + { + return json_encode($report->contents()); + } +} +``` + +Теперь класс Report отвечает только за предоставление данных, а класс JsonReportFormatter занимается только их +форматированием в JSON. Таким образом, каждый класс имеет только одну причину для изменения, что соответствует принципу +SRP.