Шаблоны проектирования. Часть 2.
Димитров Вячеслав Михайлович, старший преподаватель кафедры ИМО, dimitrov@cs.petrsu.ru
Special Case
- Замена NULL переменной
- Если переменная может принимать NULL, то нужно всегда заботиться о проверках
- Иногда полезно заменить NULL на объект с тем же интерфейсом но который ведет себя по другому.
Special Case. Пример.
public class Car {
private bookCustomer = NULL;
// ...
}
public class Customer {
// ...
abstrac public String getName();
}
Special Case. Пример.
public class Car {
private bookCustomer = new MissingCustomer();
// ...
}
public class MissingCustomer extends Customer {
// ...
public String getName() {
return "";
}
}
PubSub
- Шаблон передачи сообщений
- Отправители (publishers) публикуют сообщения (ничего не знают о подписчиках)
- Получатели ничего не знают об отправителя
- Сообщения делятся на классы
PubSub. Пример на soket.io. Сервер.
var io = require('socket.io')(http);
io.on('connection', function(socket){
socket.on('chat_message', function(msg){
console.log('message: ' + msg);
});
});
PubSub. Пример на soket.io. Клиент.
var socket = io();
socket.emit('chat_message', 'New message');
LazyLoad.
- Объектно-реляционная логика.
- Объект, не содержит данных, но знает, где их взять.
- Нет необходимости в загрузке данных пока они не потребуются.
LazyLoad. Пример.
// Cars
+----+------+---------------+-----------------+
| Id | Car | Model | book_customer_id|
+----+------+---------------+-----------------+
| 18 | car1 | model1 | 79 |
+----+------+---------------+-----------------+
// Customers
+----+------+
| Id | Name |
+----+------+
| 79 | Kate |
+----+------+
LazyLoad. Пример.
public class Car {
private bookCustomer = null;
public Car() {
// ....
// Load customer.
this.bookCustomer = Customer
.where('id', this.book_customer_id).first();
}
public Customer getBookCustomer() {
return this.bookCustomer();
}
// ...
}
LazyLoad. Пример.
public class Car {
private bookCustomer = null;
public Car() {
// ....
}
public Customer getBookCustomer() {
if (!this.bookCustomer) {
// Load customer.
this.bookCustomer = Customer
.where('id', this.book_customer_id).first();
}
return this.bookCustomer();
}
// ...
}
Identity Map
- Загружать объекты один раз и хранить их в специальных структурах данных (карте присутствия).
- При потребности в объекте, сначала ищется в карте.
- Если объекта нет, то он загружается и сохраняется в карту.
Identity Map. Пример.
public class Customer extends Model {
// ...
abstrac public String getName();
}
Identity Map. Пример.
public class LoadCustomerHelper {
private Map<id, Model> objects = new HashMap<id, Model>();
public Model getById(id) {
if (!this.objects.has(id)) {
this.loadById(id);
}
return this.objects.get(id);
}
public Model loadById(id) {
Customer c = Customer
.where('id', id).first();
this.objects.put(id, c);
}
}
Identity Map. Пример.
public class Car {
private bookCustomer = null;
public Car() {
// ....
}
public Customer getBookCustomer() {
if (!this.bookCustomer) {
// Load customer.
this.bookCustomer = LoadCustomerHelper.getInstance().getById(this.book_customer_id);
}
return this.bookCustomer();
}
// ...
}
Unit of work
- Обслуживает набор объектов, изменяемых в бизнес-транзакции.
- Каждое изменение объекта записывать в БД - много мелких запросов в БД.
- Специальный объект следит за всеми изменениями в объектах
- После завершения некоторого действия сохраняет все изменения в БД.
Web. Template View (шаблонизатор).
- Заполняет шаблон при помощи маркеров.
- Позволяет создавать HTML страницы из шаблона.
- Заменяемые данные помечаются маркером.
Web. Template View. Пример.
<h1>{{event_name}}</h1>
<span>{{event_date}}</span>
<div>
{{event_description}}
</div>
Web. Template View. Пример.
$view = View::make('template', [
'event_name' => 'Name',
'event_date' => '29.02.2020',
'event_description' => 'Description'
]);
$sHtml = $view->render();
Web. Model View Controller.
- Разделяет модель данных, пользовательский интерфейс и управляющую логику.
- Модель не зависит от представления и логики.
- Модель может иметь несколько представлений.
Web. MVC. Пример. Model.
public class Model {
public void extract() {
// ...
}
public void save() {
// ...
}
}
public class Car extends Model {
public String getDescription() {
return this.model + ' ' + this.year;
}
}
Web. MVC. Пример. Controller.
public class CarController {
public Response cars(Request request) {
$aCars = Car::orderBy('created_at')->limit(10);
return view('cars', array(
'aCars' => $aCars,
));
}
}
Web. MVC. Пример. View.
<table>
<tr>
<th>Id</th>
<th>Description</th>
</tr>
@foreach($aCars as $oCar)
<tr>
<th>{{$oCar->id}}</th>
<th>{{$oCar->getDescription()}}</th>
</tr>
@endforeach
</table>
Reactor.
Reactor. Пример NodeJS.
Reactor. Пример NodeJS.
let reloadIniFiles = function () {
// Read files from file system.
fs.readdir(configsDir, function (err, files) {
files.forEach(function (file) {
if (file.endsWith(".ini")) {
console.log(file);
loadIniFile(configsDir + file);
}
});
});
console.log('after readdir.');
// Delete service types.
deleteServiceTypes();
}
Copy-paste
- Перенос функции с похожим функционалом из одного проекта в другой,
возможно с некоторыми дополнениями.
- Ухудшается повторное использование кода
- Понижается качество кода
- Усложняется поддержка кода
Copy-paste
- Code Review значительно усложняется
- Оформление повторяющегося кода в виде библиотеки, модуля, пакета, зависимости.
Spaghetti code
- Слабо структурированная и плохо спроектированная система, запутанная и очень сложная для понимания.
- Подобный код в будущем не сможет разобрать даже его автор.
- Небольшое количество объектов.
- Огромные по размеру кода методы.
Spaghetti code
- Работает - не трогай!
- Недостаток опыта разработки.
- Решение: рефакторинг или переписать полностью.
Золотой молоток
- Уверенность в полной универсальности кода.
- Применение одного решения (чаще всего какого-либо одного шаблона проектирования) для всех задач.
- Решение: взвешенный выбор в пользу самого удачного решения.
Магические числа
- Магическое число — константа, использованная в коде для чего либо
- само число не несёт никакого смысла без соответствующего комментария
- Числа затрудняют понимание кода и его рефакторинг.
- Главными причинами этой ошибки — спешка при разработке.
Hard code
- Внедрение различных данных об окружении в реализацию.
- Что значит d:\proj\tests.dat?
- Непереносимость
- Как правило, программист практически сразу забывает где и что он захардкодил
Soft code
- Параноидальная боязнь жёсткого кодирования
- Конфигурация невероятно сложная и непрозрачная.
- Много ресурсов уходит на реализацию возможности настроек абсолютно всего.
- Развёртывание такой системы повлечет так же дополнительные затраты.
Ненужная сложность
- Заумность решения.
- Ненужная сложность может быть внесена в решение любой задачи.
- Ненужные проверки.
- Усложнению понимания кода.
- Понижению скорости работы.
Лодочный якорь
- Сохранение неиспользуемых частей системы, которые остались после оптимизации или рефакторинга.
- Оставлены «на будущее», авось придётся ещё их использовать
- Усложняет систему.
Изобретение велосипеда
- Программист разрабатывает собственное решение для задачи, для которой уже существуют решения
- Это приводит лишь к потере времени и понижению эффективности работы программиста
Изобретение одноколёсного велосипеда
- Создание своего плохого решения, при существовании лучшего.
- Тратится на изобретение и реализацию собственного решения.
- Тратится при рефакторинге таких решений и замене их оптимальными.
Программирование перебором
- А если i+1?
- Подбором параметров, порядка вызова функций и так далее
- Если программист не понимает происходящего, то он не сможет предусмотреть все варианты развития событий и обязательно о чём-то забудет.
Слепая вера
- Недостаточная проверка корректности входных данных, исправления ошибки или результатов работы кода
- Но и не следует доводить это недоверие до паранойи
Бездумное комментирование
- Большое количество лишних и неинформативных комментариев.
- Код не следует комментировать ради комментирования!
- Ни в коем случае нельзя допускать диалога разработчиков в комментариях.
Божественный объект
- Объект берет на себя слишком много функций и/или хранит в себе практически все данные
- Непереносимый код, в котором, к тому же, сложно разобраться.
- Вся система зависит практически только от него.
Принцип проектирования и разработки KISS
- Keep it simple, stupid
- не имеет смысла реализовывать дополнительные функции
- не стоит перегружать интерфейс опциями
- бессмысленно делать реализацию сложной бизнес-логики, которая учитывает абсолютно все возможные варианты поведения системы
Принцип проектирования и разработки KISS
- не имеет смысла беспредельно увеличивать уровень абстракции, надо уметь вовремя остановиться.
- бессмысленно закладывать в проект избыточные функции «про запас».
- не стоит подключать огромную библиотеку, если вам от неё нужна лишь пара функций.
- декомпозиция чего-то сложного на простые составляющие — это архитектурно верный подход.
- абсолютная математическая точность или предельная детализация нужны не всегда.
Принцип проектирования и разработки DRY
- Don’t repeat yourself
- Самый простой подход по уменьшению сложности — разделить систему на управляемые части.
- Принцип DRY требует, чтобы такие части информации встречались в вашем коде один, и только один раз.
- Каждая часть данных должна иметь четкое, надежное представление в системе.
- DRY — это философия, разбивающая логику на представления.
Принцип проектирования и разработки YAGNI
- You ain’t gonna need it – вам это не понадобится
- KISS старается искать простые решения, а YAGNI просто не делает никаких решений!
Планирование проекта
- Достичь меньшей сложности путем уменьшения уровня абстракций
- Разделить функционал от возможностей (features)
- Учесть небольшие не-функциональные требования
- Определить затратные по времени задачи, чтобы избавиться от них