From 3c44450088e3558ef252b6b472235b6eb8763c1f Mon Sep 17 00:00:00 2001 From: Alexandr Chernyaev Date: Tue, 25 Mar 2025 00:43:29 +0300 Subject: [PATCH 1/3] Added section "Getters and Setters" into library --- public/img/ui/bone.svg | 41 ++++++++ .../library/getters-and-setters.blade.php | 28 ++++++ storage/library/getters-and-setters/basics.md | 28 ++++++ .../getters-and-setters.md | 82 ++++++++++++++++ .../getters-and-setters/tell-dont-ask.md | 93 +++++++++++++++++++ 5 files changed, 272 insertions(+) create mode 100644 public/img/ui/bone.svg create mode 100644 resources/views/library/getters-and-setters.blade.php create mode 100755 storage/library/getters-and-setters/basics.md create mode 100644 storage/library/getters-and-setters/getters-and-setters.md create mode 100644 storage/library/getters-and-setters/tell-dont-ask.md diff --git a/public/img/ui/bone.svg b/public/img/ui/bone.svg new file mode 100644 index 00000000..41db671b --- /dev/null +++ b/public/img/ui/bone.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/views/library/getters-and-setters.blade.php b/resources/views/library/getters-and-setters.blade.php new file mode 100644 index 00000000..aaa32350 --- /dev/null +++ b/resources/views/library/getters-and-setters.blade.php @@ -0,0 +1,28 @@ +@extends('layout') +@section('title', 'Геттеры, сеттеры и DTO') +@section('description', 'Разрушают инкапсуляцию, увеличивают связанность кода и приводят к процедурному стилю программирования.') +@section('content') + + + Объектно-ориентированное программирование + Геттеры, сеттеры и DTO + + Разрушают инкапсуляцию, увеличивают связанность кода и приводят к процедурному стилю программирования. + + + + + + + @php + $sections = collect([ + 'basics', + 'getters-and-setters', + 'tell-dont-ask', + ]) + ->map(fn ($file) => \Illuminate\Support\Str::of($file)->start('getters-and-setters/')) + ->map(fn ($file) => new \App\Library($file)); + @endphp + + @include('particles.library-section', ['sections' => $sections]) +@endsection diff --git a/storage/library/getters-and-setters/basics.md b/storage/library/getters-and-setters/basics.md new file mode 100755 index 00000000..b582b996 --- /dev/null +++ b/storage/library/getters-and-setters/basics.md @@ -0,0 +1,28 @@ +--- +title: "Основы" +description: "В программировании существует разные подходы к организации кода." +--- + +В программировании существует множество разных подходов к организации кода. Например знакомый со школькой скамьи или университета процедурный стиль, когда программа описывается серией последовательных инструкций: «сделай 1, сделай 2, сделай 3». + +```php +step1($data); +step2($data); +step3($data); +``` + +Такой подход достаточно просто начать использовать и долгие года был основным в программировании в C, Pascal и других языках. + + +Однако в современном PHP и Laravel обычно применяется объектно-ориентированный подход. В котором мы создаём классы, инициируем их и используем методы, чтобы управлять поведением объектов. ООП основывается на идее, что данные и поведение должны быть объединены в единый «живой» объект. Например, представьте класс `App`: + +```php +$app = new App(); + +$app->start(); +``` + +В этом случае мы не задаем пошагово, что делать, а поручаем объекту выполнить всю необходимую инициализацию и запуск. +Такой подход позволяет скрыть детали реализации и предоставляет лишь интерфейс для взаимодействия с объектом. + +Но существует чертовски много кода и практик на ООП языках, который является процедурным по своей конструкции, по этому мы разберем несколько примеров. diff --git a/storage/library/getters-and-setters/getters-and-setters.md b/storage/library/getters-and-setters/getters-and-setters.md new file mode 100644 index 00000000..b5b974cd --- /dev/null +++ b/storage/library/getters-and-setters/getters-and-setters.md @@ -0,0 +1,82 @@ +--- +title: "Глупые объекты рождают процедурные участки кода" +description: "Объект как контейнер для данных." +--- + +Встречается практика, когда один объект используется исключительно как контейнер для данных – он +не содержит бизнес-логики, а только хранит информацию. Рассмотрим пример: + +```php +class User +{ + private string $name; + private string $email; + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getEmail(): string + { + return $this->email; + } + + public function setEmail(string $email): void + { + $this->email = $email; + } +} +``` + +Здесь объект `User` используется только для хранения данных. +Дальше эти данные обрабатываются в различных частях приложения. Например: + +```php +// В одном участке кода создаем объект и заполняем его данными +$user = new User(); +$user->setName('John Doe'); +$user->setEmail('john.doe@example.com'); + +// В другом участке кода валидируем данные +$validator = new Validator(); +$validator->validate($user); + +// В третьем участке кода отправляем письмо пользователю +$notification = new WelcomeNotification(); +$notification->send($user->getEmail()); +``` + +В этом примере мы видим, что объект `User` не обладает собственной логикой – за обработку данных отвечают внешние +классы, такие как `Validator` и `Mailer`. По сути, вместо использования массива для хранения информации мы применяем +объект, но бизнес-логика вынесена за его пределы. Это приближает архитектуру к процедурному стилю, где программа состоит +из набора функций, выполняющих действия над данными. + +Аналогичная ситуация наблюдается и в `ORM`, которые не используют подход `Active Record`, например, в `Doctrine`: + +```php +$entityManager = EntityManager::create($connection, $config); + +// Создаем объект пользователя +$user = new User(); +$user->setName("John Doe"); +$user->setEmail("john.doe@example.com"); + +// Сохраняем в БД +$entityManager->persist($user); +$entityManager->flush(); +``` + +Это противоречит принципам объектно-ориентированного программирования, так как объекты должны быть ответственными за +свои данные и поведение. + +В результате мы получаем систему, где логика распределена между «глупыми» объектами и процедурными участками кода. Это +затрудняет понимание, тестирование и дальнейшее развитие приложения, так как в нем присутствует неявное разделение +ответственности. + + diff --git a/storage/library/getters-and-setters/tell-dont-ask.md b/storage/library/getters-and-setters/tell-dont-ask.md new file mode 100644 index 00000000..bdb5c5d6 --- /dev/null +++ b/storage/library/getters-and-setters/tell-dont-ask.md @@ -0,0 +1,93 @@ +--- +title: "Tell, Don’t Ask" +description: "Объекты должны выполнять действия, а не только отдавать свои данные" +--- + +Tell, Don’t Ask – это один из хорошо сформулированных принципов качественного объектно-ориентированного проектирования, который помогает создать чистую и легко поддерживаемую архитектуру. + +> «Не спрашивай объект о данных, чтобы принять решение – скажи объекту, что делать.» + +На первый взгляд этот принцип может показаться противоречащим принципу единой ответственности (SRP), но в реальной разработке зачастую приходится делать выбор, какой из принципов важнее в конкретном контексте. Тем не менее, чем больше принципов удается соблюсти одновременно, тем лучше. + +Вместо того чтобы создавать «глупый» объект, мы можем инкапсулировать бизнес-логику прямо в объекте или в связанном сервисе. Например, если нам необходимо валидировать и отправить приветственное письмо, можно поручить это объекту или сервису, который знает, как с ним работать. Рассмотрим два варианта: + +Вместо того чтобы извлекать данные и проверять их во внешнем коде, объект сам выполняет необходимое действие. + +```php +class User +{ + protected string $name; + protected string $email; + + public function register(): void + { + $validator = Validator::make($data, [ + 'name' => 'required', + 'email' => 'required|email', + ]); + + if ($validator->fails()) { + throw new RuntimeException("Данные не корректны"); + } + + //... + + $this->notify(WelcomeNotification::class) + } +} + + +$user = new User( + name: "John Doe", + email: "john.doe@example.com" +); + +$user->register(); +``` + +Здесь объект сам выполняет проверку и отправляет уведомление, а не предоставляет данные для обработки извне. + + +Если объект не должен нести на себе всю логику, её можно вынести в сервис, передавая туда объект: + +```php +class UserService +{ + public function __construct( + private Validator $validator, + private Mailer $mailer + ) {} + + public function register(array $data): User + { + $this->validator->validate($data, [ + 'name' => 'required', + 'email' => 'required|email', + ]); + + if (this->validator->fails()) { + throw new RuntimeException("Данные не корректны"); + } + + $user = new User($data); + + //... + + $this->mailer->sendWelcomeMessage($user->email); + + return $user; + } +} + +$user = app(UserService::class)->register([ + 'name' => 'John Doe', + 'email' => 'john.doe@example.com', +]); +``` + +Здесь объект `UserService` выполняет логику, а объект `User` остаётся максимально простым. + + +> {tip} К тому же принцип **Tell, Don’t Ask** хорошо сочетается с **Законом Деметры** (*Law of Demeter, LoD*), который также способствует созданию более устойчивых и независимых объектов. + +Можно узнать больше из работ [Мартина Фаулера](https://martinfowler.com/bliki/TellDontAsk.html), [Аллена Холуба](https://www.infoworld.com/article/2161183/why-getter-and-setter-methods-are-evil.html) и [Егора Бугаенко](https://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html). From 988efd42942c0dcb5167db0538978b2f1f5c451a Mon Sep 17 00:00:00 2001 From: Alexandr Chernyaev Date: Wed, 26 Mar 2025 23:58:51 +0300 Subject: [PATCH 2/3] Fixed ambiguous or incorrect remarks --- .../library/getters-and-setters.blade.php | 4 +- storage/library/getters-and-setters/basics.md | 19 +++-- .../getters-and-setters.md | 2 + .../getters-and-setters/tell-dont-ask.md | 74 +++++-------------- 4 files changed, 35 insertions(+), 64 deletions(-) diff --git a/resources/views/library/getters-and-setters.blade.php b/resources/views/library/getters-and-setters.blade.php index aaa32350..fc5f9a85 100644 --- a/resources/views/library/getters-and-setters.blade.php +++ b/resources/views/library/getters-and-setters.blade.php @@ -1,11 +1,11 @@ @extends('layout') -@section('title', 'Геттеры, сеттеры и DTO') +@section('title', 'Геттеры и сеттеры') @section('description', 'Разрушают инкапсуляцию, увеличивают связанность кода и приводят к процедурному стилю программирования.') @section('content') Объектно-ориентированное программирование - Геттеры, сеттеры и DTO + Геттеры и сеттеры в объектах Разрушают инкапсуляцию, увеличивают связанность кода и приводят к процедурному стилю программирования. diff --git a/storage/library/getters-and-setters/basics.md b/storage/library/getters-and-setters/basics.md index b582b996..d40a7d76 100755 --- a/storage/library/getters-and-setters/basics.md +++ b/storage/library/getters-and-setters/basics.md @@ -3,7 +3,9 @@ title: "Основы" description: "В программировании существует разные подходы к организации кода." --- -В программировании существует множество разных подходов к организации кода. Например знакомый со школькой скамьи или университета процедурный стиль, когда программа описывается серией последовательных инструкций: «сделай 1, сделай 2, сделай 3». +В программировании существует множество разных подходов к организации кода. Например знакомый со школьной скамьи или +университета процедурный стиль, когда программа описывается серией последовательных инструкций: «сделай 1, сделай 2, +сделай 3». ```php step1($data); @@ -11,10 +13,11 @@ step2($data); step3($data); ``` -Такой подход достаточно просто начать использовать и долгие года был основным в программировании в C, Pascal и других языках. +Этот стиль программирования прост в освоении и долгие годы был основным в программировании в C, Pascal и других языках. - -Однако в современном PHP и Laravel обычно применяется объектно-ориентированный подход. В котором мы создаём классы, инициируем их и используем методы, чтобы управлять поведением объектов. ООП основывается на идее, что данные и поведение должны быть объединены в единый «живой» объект. Например, представьте класс `App`: +Однако в современном PHP и Laravel обычно применяется объектно-ориентированный подход. В котором мы создаём классы, +инициируем их и используем методы, чтобы управлять поведением объектов. ООП основывается на идее, что данные и поведение +должны быть объединены в единый «живой» объект. Например, представьте класс `App`: ```php $app = new App(); @@ -22,7 +25,9 @@ $app = new App(); $app->start(); ``` -В этом случае мы не задаем пошагово, что делать, а поручаем объекту выполнить всю необходимую инициализацию и запуск. -Такой подход позволяет скрыть детали реализации и предоставляет лишь интерфейс для взаимодействия с объектом. +В этом случае мы не указываем пошагово, что необходимо делать, а поручаем объекту выполнить всю необходимую +инициализацию и запуск (инкапсуляция). Такой подход позволяет скрыть детали реализации и предоставляет лишь интерфейс +для взаимодействия с объектом. -Но существует чертовски много кода и практик на ООП языках, который является процедурным по своей конструкции, по этому мы разберем несколько примеров. +Но существует чертовски много кода и практик на ООП языках, который является процедурным по своей конструкции. Поэтому +мы разберем несколько примеров. diff --git a/storage/library/getters-and-setters/getters-and-setters.md b/storage/library/getters-and-setters/getters-and-setters.md index b5b974cd..cc20d813 100644 --- a/storage/library/getters-and-setters/getters-and-setters.md +++ b/storage/library/getters-and-setters/getters-and-setters.md @@ -34,6 +34,8 @@ class User } ``` +> {note} Часто такой класс ошибочно называют "Data Transfer Object" (DTO), но это не так. DTO должен быть иммутабельным, а наличие сеттеров нарушает этот принцип. + Здесь объект `User` используется только для хранения данных. Дальше эти данные обрабатываются в различных частях приложения. Например: diff --git a/storage/library/getters-and-setters/tell-dont-ask.md b/storage/library/getters-and-setters/tell-dont-ask.md index bdb5c5d6..1e4042ea 100644 --- a/storage/library/getters-and-setters/tell-dont-ask.md +++ b/storage/library/getters-and-setters/tell-dont-ask.md @@ -18,24 +18,29 @@ class User { protected string $name; protected string $email; - - public function register(): void + + private function validate(): bool { - $validator = Validator::make($data, [ - 'name' => 'required', + $validator = Validator::make([ + 'name' => $this->name, + 'email' => $this->email, + ], [ + 'name' => 'required', 'email' => 'required|email', ]); - - if ($validator->fails()) { - throw new RuntimeException("Данные не корректны"); - } - - //... - - $this->notify(WelcomeNotification::class) - } -} + return $validator->passes() + } + + public function register(): void + { + $this->validate(); + + // Сохранение пользователя в БД... + + $this->notify(WelcomeNotification::class); + } +} $user = new User( name: "John Doe", @@ -47,47 +52,6 @@ $user->register(); Здесь объект сам выполняет проверку и отправляет уведомление, а не предоставляет данные для обработки извне. - -Если объект не должен нести на себе всю логику, её можно вынести в сервис, передавая туда объект: - -```php -class UserService -{ - public function __construct( - private Validator $validator, - private Mailer $mailer - ) {} - - public function register(array $data): User - { - $this->validator->validate($data, [ - 'name' => 'required', - 'email' => 'required|email', - ]); - - if (this->validator->fails()) { - throw new RuntimeException("Данные не корректны"); - } - - $user = new User($data); - - //... - - $this->mailer->sendWelcomeMessage($user->email); - - return $user; - } -} - -$user = app(UserService::class)->register([ - 'name' => 'John Doe', - 'email' => 'john.doe@example.com', -]); -``` - -Здесь объект `UserService` выполняет логику, а объект `User` остаётся максимально простым. - - > {tip} К тому же принцип **Tell, Don’t Ask** хорошо сочетается с **Законом Деметры** (*Law of Demeter, LoD*), который также способствует созданию более устойчивых и независимых объектов. Можно узнать больше из работ [Мартина Фаулера](https://martinfowler.com/bliki/TellDontAsk.html), [Аллена Холуба](https://www.infoworld.com/article/2161183/why-getter-and-setter-methods-are-evil.html) и [Егора Бугаенко](https://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html). From e58a5bb56e9afb5c04ac732e3107cead8628f7bd Mon Sep 17 00:00:00 2001 From: Alexandr Chernyaev Date: Thu, 27 Mar 2025 00:00:00 +0300 Subject: [PATCH 3/3] Added route endpoint --- routes/web.php | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/web.php b/routes/web.php index de288452..8cb3320e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -65,6 +65,7 @@ Route::view('/library/collection', 'library.collection')->name('library.collection'); Route::view('/library/solid', 'library.solid')->name('library.solid'); Route::view('/library/actions', 'library.actions')->name('library.actions'); +Route::view('/library/getters-and-setters', 'library.getters-and-setters')->name('library.getters-and-setters'); /* |-------------------------------------------------------------------------- | Open Quiz