По началу хотел здесь написать о контейнере внедрения зависимостей в
Yii2, но немного почитав понял, что гораздо важнее не умение
использовать конкретную реализацию контейнера внедрения зависимостей,
которая предложена разработчиками Yii2, а уметь применять принцип
инверсии зависимостей в совокупности с принципом программирования по
контракту.
Для того, чтобы отделить бизнес-логику программы от обычных объектов я сделал папку app/models/logic получилась следующая структура файлов и папок:
Конечно этот подход кажется немного утомительным, потому что приходится создавать контракты и постоянно отделять объекты от логики приложения, а ведь частенько хочется просто создать объект и использовать его все время!
Но этот подход помогает улучшить общую архитектуру приложения! И насколько я заметил при использовании инверсии зависимостей часто появляются классы, в именах которых есть слово Service, это говорит именно о том, что абстракция данного класса настолько высока, что ему не важно какие конкретно объекты получают его методы.
В заключении посмотрим как можно использовать созданный нами пример в контроллере:
Еще хочется сказать, что такая разбивка логики очень сильно способствует упрощению тестирования, потому что гораздо легче написать модульный тест для класса UserService , чем мучиться попытками протестировать жесткую логику находящуюся в самом контроллере!
Зачем это нужно?
У меня есть знакомый, который постоянно мне задает этот вопрос "А зачем все это нужно?". В данном случае принцип инверсии зависимостей необходим для создания слабого зацепления (англ. loose coupling). То есть в вашем коде не будет жесткой зависимости от каких-либо конкретных объектов, потому что логика вашей программы будет завязана на использовании интерфейсов( их же я называю и контрактами ), в случае если объект "Хочет" работать с другим вашим классом, который реализует бизнес-логику, то этот объект должен будет принять правила интерфейса(контракта).Рассмотрим пример
создадим таблицу UsersCREATE TABLE `users` (
`id` int(11) NOT NULL,
`name` varchar(50) NOT NULL,
`email` varchar(50) NOT NULL,
`lastname` varchar(50) NOT NULL
);
дальше сгенерируем модель Users c помощью gii, заходим на роут ?r=gii выбираем
генератор моделей и если у на есть подключение к БД, вписываем первую
букву таблицы "u" и gii сам заполнит необходимые поля, нажимаем
"Generate" и идем дальше.Для того, чтобы отделить бизнес-логику программы от обычных объектов я сделал папку app/models/logic получилась следующая структура файлов и папок:
- app
- models
-logic
- user
- UserContract.php // Интерфейс
- UserService.php // Бизнес логика работы с пользователями
в UserContract.php я поместил следующий код:<?php
namespace app\models\logic\user;
interface UserContract {
public function getName();
public function getLastName();
public function getEmail();
}
В UserService.php - следующий:<?php
namespace app\models\logic\user;
class UserService {
public function getUser(UserContract $user) {
return [
'name' => $user->getName(),
'email' => $user->getEmail(),
'lastName' => $user->getLastName(),
];
}
}
Из кода видно, что наша логика работает именно с ИНТЕРФЕЙСОМ! И каждый объект, который "захочет" поучаствовать в предоставлении данных в логику метода UserService::getUser должен реализовать интерфейс/контракт UserConctract.Конечно этот подход кажется немного утомительным, потому что приходится создавать контракты и постоянно отделять объекты от логики приложения, а ведь частенько хочется просто создать объект и использовать его все время!
Но этот подход помогает улучшить общую архитектуру приложения! И насколько я заметил при использовании инверсии зависимостей часто появляются классы, в именах которых есть слово Service, это говорит именно о том, что абстракция данного класса настолько высока, что ему не важно какие конкретно объекты получают его методы.
В заключении посмотрим как можно использовать созданный нами пример в контроллере:
<?php
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller {
public function actionUserView($id) {
$user = \app\models\Users::findOne($id);
if(empty($user)) { return 'error'; }
$userService= new \app\models\logic\user\UserService();
$userData = $userService->getUser($user);
return $this->render('/user/view',compact('userData'));
}
}
В результате в $userData в виде Мы получаем массив с данными о пользователе!Еще хочется сказать, что такая разбивка логики очень сильно способствует упрощению тестирования, потому что гораздо легче написать модульный тест для класса UserService , чем мучиться попытками протестировать жесткую логику находящуюся в самом контроллере!
Комментариев нет:
Отправить комментарий