Шаблоны проектирования в программировании

ШАБЛОНЫ ПРОЕКТИРОВАНИЯ В ПРОГРАММИРОВАНИИ

Кошелев О.В.

Кошелев Олег Вячеславович — бакалавр, кафедра вычислительной техники, Московский технический университет радиотехники, электроники и автоматики, г. Москва

Аннотация: в этой статье рассказывается про наиболее известные шаблоны проектирования в программировании, подробно описываются особенности каждого шаблона и в каких случаях каждый шаблон лучше применим. Также представлен наглядный листинг программного кода, показывающий, как применяется шаблон на практике. Ключевые слова: шаблоны проектирования, современные подходы в программировании.

Введение

С каждым днём веб-технологи набирают всё больше популярности и актуальности, следовательно появляется огромное число различных подходов к разработке программного кода и структурирования данных.

По мере разработки программисты стали сталкиваться с рядом повторяющихся проблем, и следовательно были разработаны шаблоны (подходы к структуризации и разработки кода), которые помогают обойти их в ходе разработки. И так, Шаблон проектирования, или паттерн -в разработке программного обеспечения — повторяемая архитектурная конструкция, представляющая собой решение проблемы проектирования, в рамках некоторого часто возникающего контекста.

2. Типы шаблонов

2.1. Порождающие

Порождающие - шаблоны, которые направлены на создание экземпляра объекта. Решают проблему больших сложных конструкций, которые получаются при обычной форме создания объектов.

Существуют следующие порождающие шаблоны:

2.1.1. Простая фабрика

В объектно-ориентированном программировании (ООП), фабрика — это объект для создания других объектов. Формально фабрика — это функция или метод, который возвращает объекты изменяющегося прототипа или класса из некоторого вызова метода, который считается «новым».

В данном шаблоне происходит генерация экземпляра для клиента, оставляя не раскрытой логику.

interface Door{

public function getWidth(): float; public function getHeight(): float;

}

class WoodenDoor implements Door{ protected $width; protected $height;

public function_construct(float $width, float $height){

$this->width = $width; $this->height = $height;

}

public function getWidth(): float{ return $this->width;

}

public function getHeight(): float{

return $this->height; }}

Затем у нас есть наша DoorFactory, которая делает дверь и возвращает её:

class DoorFactory{

public static function makeDoor($width, $height): Door { return new WoodenDoor($width, $height);

}}

И затем мы можем использовать всё это:

Sdoor = DoorFactory::makeDoor(100, 200); echo 'Width: ' . $door->getWidth(); echo 'Height: ' . $door->getHeight();

2.1.2. Одиночка

Одиночка — порождающий шаблон проектирования, гарантирующий, что в однопроцессном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру.

То есть 1 экземпляр класса обрабатывает все обращения и запрещает создавать второй экземпляр класса.

У паттерна одиночка есть определенные достоинства: контролируемый доступ к единственному экземпляру. Поскольку класс Singleton инкапсулирует свой единственный экземпляр, он полностью контролирует то, как и когда клиенты получают доступ к нему; а уменьшение числа имен. Паттерн одиночка - шаг вперед по сравнению с глобальными переменными. Он позволяет избежать засорения пространства имен глобальными переменными, в которых хранятся уникальные экземпляры; а допускает уточнение операций и представления. От класса Singleton можно порождать подклассы, а приложение легко сконфигурировать экземпляром расширенного класса. Можно конкретизировать приложение экземпляром того класса, который необходим во время выполнения; а допускает переменное число экземпляров. Паттерн позволяет вам легко изменить свое решение и разрешить появление более одного экземпляра класса [2, с. 132].

Singleton. Вы можете применять один и тот же подход для управления числом экземпляров, используемых в приложении. Изменить нужно будет лишь операцию, дающую доступ к экземпляру класса Singleton; а большая гибкость, чем у операций класса. Еще один способность реализовать функциональность одиночки - использовать операции класса, то есть статические функции-члены в C++ и методы класса в Smalltalk. Но оба этих приема препятствуют изменению дизайна, если потребуется разрешить наличие нескольких экземпляров класса. Кроме того, статические функции-члены в C++ не могут быть виртуальными.

Вообще шаблон одиночка признан антипаттерном, необходимо избегать его чрезмерного использования. Он необязательно плох и может иметь полезные применения, но использовать его надо с осторожностью, потому что он вводит глобальное состояние в ваше приложение и его изменение в одном месте может повлиять на другие части приложения, что вызовет трудности при отладке. Другой минус — это то, что он делает ваш код связанным. final class President{

private static $instance;

private function_construct(){

// Прячем конструктор

}

public static function getInstance(): President{ if (!self::$instance) {

self::$instance = new self();

}

return self:: $instance;

}

private function_clone() {

// Отключаем клонирование

}

private function wakeup( { // Отключаем десериализацию

}}

Пример использования: $president1 = President::getInstance(); $president2 = President::getInstance(); var_dump($president1 === $president2); // true

2.1.3. Строитель

Строитель — порождающий шаблон проектирования, который предоставляет способ создания составного объекта. Предназначен для решения проблемы антипаттерна «Телескопический конструктор».

Шаблон позволяет создать различные виды объектов, при этом он избегает засорения конструктора. Он полезен, когда может быть несколько видов объекта или когда необходимо множество шагов, связанных с его созданием.

Перейдем сразу к примеру в коде. Адекватной альтернативой будет использование шаблона «Строитель». Сначала у нас есть Burger, который мы хотим создать: class Burger{ protected $size;

protected $cheese = false; protected $pepperoni = false;

public function_construct(BurgerBuilder $builder){

$this->size = $builder->size; $this->cheese = $builder->cheese; $this->pepperoni = $builder->pepperoni; $this->lettuce = $builder->lettuce; $this->tomato = $builder->tomato;

}}

Берём строитель class BurgerBuilder{ public $size; public $cheese = false; public $pepperoni = false;

public function_construct(int $size){

$this->size = $size;

}

public function build(): Burger{ return new Burger($this);

}}

И начинаем создавать $burger = (new BurgerBuilder(14)) ->addPepperoni() ->addLettuce() ->addTomato() ->build();

Лучше всего использовать, когда необходим на этапе написания кода новый объект, похожий на текущий.

2.2. Структурные шаблоны

Шаблоны проектирования, в которых рассматривается вопрос о том, как из классов и объектов образуются более крупные структуры. Другими словами, они тесно связаны с композицией объектов и показывают как сущности могут использовать друг друга. Существуют следующие структурные шаблоны: 2.2.1. Адаптер

Адаптер — структурный шаблон проектирования, предназначенный для организации использования функций объекта, недоступного для модификации, через специально созданный интерфейс.

Шаблон позволяет обернуть несовместимые объекты в адаптер, чтобы сделать их совместимыми с другим классом.

Обратимся к коду. Представим игру, в которой охотник охотится на львов. Изначально у нас есть интерфейс Lion, который реализует всех львов interface Lion{

public function roar();

}

class AfricanLion implements Lion{

public function roar( { }}

class AsianLion implements Lion{

public function roar(){ }}

И Hunter охотится на любую реализацию интерфейса Lion:

45

class Hunter{

public function hunt(Lion $lion){ }}

Теперь представим, что нам надо добавить WildDog в нашу игру, на которую наш НиПегтакже мог бы охотиться. Но мы не можем сделать это напрямую, потому что у WildDogдругой интерфейс. Чтобы сделать её совместимой с нашим Hunter, нам надо создать адаптер:

Способ применения: $wildDog = new WildDog();

$wildDogAdapter = new WildDogAdapter($wildDog); $hunter = new Hunter(); $hunter->hunt

Мост — структурный шаблон проектирования, используемый в проектировании программного обеспечения.

Его цель - Отделить абстракцию от её реализации так, что они могут изменяться независимо друг от друга.

Представим, что у вас есть сайт с разными страницами, и вам надо разрешить пользователям менять их тему. Что вы будете делать? Создавать множественные копии каждой страницы для каждой темы или просто отдельную тему, которую пользователь сможет выбрать сам? Шаблон мост позволяет вам сделать второе.

Шаблон мост — это предпочтение композиции над наследованием. Детали реализации передаются из одной иерархии в другой объект с отдельной иерархией.

Обратимся к примеру в коде. Возьмем пример с нашими страницами. У нас есть иерархия WebPage: interface WebPage{

public function_construct(Theme $theme);

public function getContent();

}

class About implements WebPage{ protected $theme;

public function_construct(Theme $theme){

$this->theme = $theme;

}

public function getContent(){

return "Страница с информацией в " . $this->theme->getColor();

}}

class Careers implements WebPage{ protected $theme;

public function_construct(Theme $theme){

$this->theme = $theme;

}

public function getContent(){

return "Страница карьеры в " . $this->theme->getColor();

}}

И отдельная иерархия Theme: interface Theme{

public function getColor();

}

class DarkTheme implements Theme{ public function getColor( { return 'темной теме';

}}

class LightTheme implements Theme{ public function getColor(){

return 'светлой теме'; }}

class AquaTheme implements Theme{ public function getColor(){

return 'голубой теме';

}}

Применение в коде: SdarkTheme = new DarkTheme(); $about = new About($darkTheme); $careers = new Careers($darkTheme);

echo $about->getContent(); // "Страница информации в темной теме"; echo $careers->getContent(); // "Страница карьеры в темной теме"; 2.3. Поведенческие шаблоны

Поведенческие шаблоны проектирования определяют общие закономерности связей между объектами, реализующими данные паттерны. Следование этим шаблонам уменьшает связность системы и облегчает коммуникацию между объектами, что улучшает гибкость программного продукта.

2.3.1. Посредник

Посредник — поведенческий шаблон проектирования, обеспечивающий взаимодействие множества объектов, формируя при этом слабую связанность, и избавляя объекты, от необходимости явно ссылаться друг на друга. Он позволяет снизить связность множества компонентов, работающих совместно. Объектам больше нет нужды вызывать друг друга напрямую. Это хорошая альтернатива Наблюдателю, если у вас есть «центр интеллекта» вроде контроллера (но не в смысле MVC).

Шаблон посредник подразумевает добавление стороннего объекта (посредника) для управления взаимодействием между двумя объектами (коллегами). Шаблон помогает уменьшить связанность (coupling) классов, общающихся друг с другом, ведь теперь они не должны знать о реализациях своих собеседников.

Разберем пример в коде. Простейший пример: чат (посредник), в котором пользователи (коллеги) отправляют друг другу сообщения.

Изначально у нас есть посредник ChatRoomMediator: interface ChatRoomMediator {

public function showMessage(User $user, string $message);

}

// Посредник

class ChatRoom implements ChatRoomMediator{

public function showMessage(User $user, string $message{ $time = date('M d, y H:i'); $sender = $user->getName(); echo $time . '[' . $sender . ']:' . $message;

}}

Затем у нас есть наши User (коллеги): class User {

protected $name; protected $chatMediator;

public function_construct(string $name, ChatRoomMediator $chatMediator) {

$this->name = $name; $this->chatMediator = $chatMediator;

}

public function getName() { return $this->name;

}

public function send($message) {

$this->chatMediator->showMessage($this, $message);

}}

Пример использования:

$mediator = new ChatRoom();

$john = new User('John Doe', $mediator);

$jane = new User('Jane Doe', $mediator);

$john->send('Привет! ');

$jane->send('Привет!');

// Вывод

// Feb 14, 10:58 [John]: Привет!

// Feb 14, 10:58 [Jane]: Привет!

2.3.2. Хранитель

Шаблон Хранитель реализуется тремя объектами: «Создателем» (originator), «Опекуном» (caretaker) и «Хранитель» (memento).

Хранитель - это объект, который хранит конкретный снимок состояния некоторого объекта или ресурса: строки, числа, массива, экземпляра класса и так далее. Уникальность в данном случае подразумевает не запрет на существование одинаковых состояний в разных снимках, а то, что состояние можно извлечь в виде независимой копии. Любой объект, сохраняемый в Хранителе, должен быть полной копией исходного объекта, а не ссылкой на исходный объект. Сам объект Хранитель является «непрозрачным объектом» (тот, который никто не может и не должен изменять).

Создатель — это объект, который содержит в себе актуальное состояние внешнего объекта строго заданного типа и умеет создавать уникальную копию этого состояния, возвращая её, обёрнутую в объект Хранителя. Создатель не знает истории изменений. Создателю можно принудительно установить конкретное состояние извне, которое будет считаться актуальным. Создатель должен позаботиться о том, чтобы это состояние соответствовало типу объекта, с которым ему разрешено работать. Создатель может (но не обязан) иметь любые методы, но они не могут менять сохранённое состояние объекта.

Опекун управляет историей снимков состояний. Он может вносить изменения в объект, принимать решение о сохранении состояния внешнего объекта в Создателе, запрашивать от Создателя снимок текущего состояния, или привести состояние Создателя в соответствие с состоянием какого-то снимка из истории.

Обратимся к коду. Возьмем наш пример текстового редактора, который время от времени сохраняет состояние, которое вы можете восстановить.

Изначально у нас есть наш объект EditorMemento, который может содержать состояние редактора:

class EditorMemento {

protected $content;

public function_construct(string $content{

$this->content = $content;

}

public function getContent({ return $this->content;

}}

Затем у нас есть наш Editor (создатель), который будет использовать объект хранитель: class Editor{

protected $content = '';

public function type(string $words){

$this->content = $this->content . ' ' . $words;

}

public function getContent(){ return $this->content;

}

public function save(){

return new EditorMemento($this->content);

}

public function restore(EditorMemento $memento) { $this->content = $memento->getContent();

}}

Пример использования: $editor = new Editor(); // Печатаем что-нибудь $editor->type(^TO первое предложение.'); $editor->type(^TO второе.');

// Сохраняем состояние для восстановления : Это первое предложение. Это второе. $saved = $editor->save(); // Печатаем ещё $editor->type(B это третье.');

// Вывод: Данные до сохранения

echo $editor->getContent(); // Это первое предложение. Это второе. И это третье.

// Восстановление последнего сохранения

$editor->restore($saved);

$editor->getContent(); // Это первое предложение. Это второе. 3. Заклюение

В современном мире программирования существует множество технологий и шаблонов и подходов к разработке программного продукта. В данной статье были рассмотрены лишь некоторые из них. У всех из перечисленных шаблонов проектирования своя особенность, но конечный результат общий - это так или иначе структурированный программный продукт.

Какой лучше или какой удобней - сказать трудно и относиться к ним нужно не как к панацее от всех ошибок в программировании, а лишь как к инструменту в руках программиста, который должен умело применять тот или иной метод исходя из ситуации.

Список литературы

1. Фримен Эрик, Фримен Элизабет, Сьерра Кэтти, Бейтс Берт. Паттерны проектирования. Питер, 2017. 656 с.

2. Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования. Санкт-Петербург, Благодатная ул., д. 67, 2001. 359 с.

3. Босуэлл Дастин, Фаучер Тревор. Читаемый код, или Программирование как искусство. Питер, 2012. 208 с.

ОБНАРУЖЕНИЕ И ЛОКАЛИЗАЦИЯ ДЕФЕКТОВ ДОРОЖНОГО ПОКРЫТИЯ ПО РЕЗУЛЬТАТАМ ОБРАБОТКИ ИНФОРМАЦИИ СПУТНИКОВЫХ И ИНЕРЦИАЛЬНЫХ НАВИГАЦИОННЫХ СИСТЕМ

Хардиков А.Е.

Хардиков Антон Евгеньевич — бакалавр, кафедра программного обеспечения вычислительной техники и автоматизированных систем, факультет информатики и вычислительной техники, Белгородский государственный технологический университет им. В.Г. Шухова, г. Белгород

Аннотация: в статье анализируется проблема контроля технического состояния автомобильных дорог; описываются подходы для решения данной проблемы и их недостатки; предлагается реализуемый альтернативный вариант для решения поставленной проблемы с большей эффективностью и меньшими затратами ресурсов.

Ключевые слова: навигационные системы, дорожное полотно, автомобильный транспорт.

На данный момент автотранспорт является одним из самых значимых среди других видов транспорта при перевозке пассажиров. На его долю приходится до 82% мирового пассажирооборота [1]. В России на автомобильный транспорт в 2014 году пришлось 68% всех перевезенных грузов и 60% всех пассажиров [2]. Такая большая популярность автомобилей вызвана, прежде всего, их удобством, и доступностью. Ведь доставка практически любого груза до конечного потребителя осуществляется с помощью автомобильного транспорта. Также с развитием навигационных спутниковых технологий стала эффективней и логистика автоперевозок. С использованием спутниковых систем могут выбираться оптимальные маршруты, и машина может находиться при этом под контролем оператора-логиста.

Для эффективного и безопасного использования автомобильного транспорта необходимо, чтобы автотрасса была в хорошем состоянии. Со временем дороги разрушаются, несмотря на высокое качество и безупречность их прокладки. Ухудшение состояния дорожного покрытия приводит к снижению средней скорости движения, увеличению количества аварийных ситуаций и, как следствие, к дополнительным затратам по времени доставки грузов и пассажиров, а также повышению стоимости перевозок. Воздействующими факторами на дорожное полотно являются: давление колес машин, особенно со значительной массой;

49