-
Notifications
You must be signed in to change notification settings - Fork 19
Added section "Getters and Setters" into library #279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| @extends('layout') | ||
| @section('title', 'Геттеры, сеттеры и DTO') | ||
| @section('description', 'Разрушают инкапсуляцию, увеличивают связанность кода и приводят к процедурному стилю программирования.') | ||
| @section('content') | ||
|
|
||
| <x-header align="align-items-center"> | ||
| <x-slot name="sup">Объектно-ориентированное программирование</x-slot> | ||
| <x-slot name="title">Геттеры, сеттеры и DTO</x-slot> | ||
| <x-slot name="description"> | ||
| Разрушают инкапсуляцию, увеличивают связанность кода и приводят к процедурному стилю программирования. | ||
| </x-slot> | ||
| <x-slot name="content"> | ||
| <img src="/img/ui/bone.svg" class="img-fluid d-block mx-auto"> | ||
| </x-slot> | ||
| </x-header> | ||
|
|
||
| @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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| --- | ||
| title: "Основы" | ||
| description: "В программировании существует разные подходы к организации кода." | ||
| --- | ||
|
|
||
| В программировании существует множество разных подходов к организации кода. Например знакомый со школькой скамьи или университета процедурный стиль, когда программа описывается серией последовательных инструкций: «сделай 1, сделай 2, сделай 3». | ||
|
|
||
| ```php | ||
| step1($data); | ||
| step2($data); | ||
| step3($data); | ||
| ``` | ||
|
|
||
| Такой подход достаточно просто начать использовать и долгие года был основным в программировании в C, Pascal и других языках. | ||
tabuna marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| Однако в современном PHP и Laravel обычно применяется объектно-ориентированный подход. В котором мы создаём классы, инициируем их и используем методы, чтобы управлять поведением объектов. ООП основывается на идее, что данные и поведение должны быть объединены в единый «живой» объект. Например, представьте класс `App`: | ||
|
|
||
| ```php | ||
| $app = new App(); | ||
|
|
||
| $app->start(); | ||
| ``` | ||
|
|
||
| В этом случае мы не задаем пошагово, что делать, а поручаем объекту выполнить всю необходимую инициализацию и запуск. | ||
|
||
| Такой подход позволяет скрыть детали реализации и предоставляет лишь интерфейс для взаимодействия с объектом. | ||
|
|
||
| Но существует чертовски много кода и практик на ООП языках, который является процедурным по своей конструкции, по этому мы разберем несколько примеров. | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(); | ||
| ``` | ||
|
|
||
| Это противоречит принципам объектно-ориентированного программирования, так как объекты должны быть ответственными за | ||
| свои данные и поведение. | ||
|
|
||
| В результате мы получаем систему, где логика распределена между «глупыми» объектами и процедурными участками кода. Это | ||
| затрудняет понимание, тестирование и дальнейшее развитие приложения, так как в нем присутствует неявное разделение | ||
| ответственности. | ||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| --- | ||
| title: "Tell, Don’t Ask" | ||
| description: "Объекты должны выполнять действия, а не только отдавать свои данные" | ||
| --- | ||
|
|
||
| <mark>Tell, Don’t Ask</mark> – это один из хорошо сформулированных принципов качественного объектно-ориентированного проектирования, который помогает создать чистую и легко поддерживаемую архитектуру. | ||
|
|
||
| > «Не спрашивай объект о данных, чтобы принять решение – скажи объекту, что делать.» | ||
|
|
||
| На первый взгляд этот принцип может показаться противоречащим принципу единой ответственности (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). | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Очень хороший голосовой комментарий от Чубарова, в котором он указал, что я поддался распространённому заблуждению. Мой пример с классом
Userне является DTO, поскольку как DTO, так и VO должны быть имутабельными, так как у него есть сеттер, то он уже не может считаться таковым.Нужно добавить будет сноску об этом, а заголовок поменять