Green-sell.info

Новые технологии
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Паттерны программирования c

Осваиваем паттерны проектирования на C#. Часть 1

Первые шаблоны проектирования для языка программирования Smalltalk представили в 1987 г. Кэнт Бэк и Вард Каннингем. Но это было только началом, настоящее признание в мире программирования они получили после публикации книги «Приемы объектно-ориентированного проектирования. Паттерны проектирования», написанной программистами Э. Гаммом, Р. Хелмом, Р. Джонсоном и Дж. Влиссидесом.

В ней были представлены 23 шаблона, ставших сейчас основными. Данная работа дала толчок к изучению паттернов программистами. Издание «банды четырех» (так в шутку прозвали авторов книги) до сих пор остается одним из ИТ-бестселлеров, и его постоянно публикуют.

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

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

«Банда четырех» разделила паттерны проектирования на три основные группы:

Кстати, в наши дни несложно выделить и другие группы и даже антипаттерны, подсказывающие, как не надо разрабатывать ПО.

Теперь выберем для обсуждения какой-нибудь паттерн, например абстрактную фабрику. Так, Abstract Factory — это один из самых известных порождающих шаблонов проектирования. Он позволяет разработчику создать интерфейс для объектов, каким-либо образом связанных между собой. Причем не требуется указывать конкретные классы, поскольку работать с каждым из них можно будет через этот интерфейс. С помощью такой фабрики удастся создавать группы объектов, реализующих общее поведение. Преимущество данного паттерна заключается в том, что он изолирует конкретные классы, благодаря чему легко заменять семейства продуктов. А к его недостаткам следует отнести то, что при расширении возможностей фабрики путем добавления нового типа продуктов придется редактировать все конкретные реализации Abstract Factory, а это порой бывает недопустимо, например, если уже создано 100 конкретных фабрик.

А теперь рассмотрим, как на практике можно использовать этот паттерн. Сначала создадим абстрактную фабрику CarFactory, содержащую семейство из двух объектов — автомобиля и двигателя для него.

abstract class CarFactory
<
public abstract AbstractCar CreateCar();
public abstract AbstractEngine CreateEngine();
>

В результате у нас появился абстрактный класс с двумя методами, обеспечивающими получение соответствующих абстрактных объектов. Теперь реализуем первую конкретную фабрику, создающую класс, описывающий автомобиль BMW и двигатель для него:

class BMWFactory : CarFactory
<
public override AbstractCar CreateCar()
<
return new BMWCar();
>
public override AbstractEngine CreateEngine()
<
return new BMWEngine();
>
>

Сделаем то же самое для автомобиля марки Audi, чтобы у нас возникла вторая конкретная фабрика:

class AudiFactory : CarFactory
<
public override AbstractCar CreateCar()
<
return new AudiCar();
>
public override AbstractEngine CreateEngine()
<
return new AudiEngine();
>
>

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

abstract class AbstractCar
<
public abstract void MaxSpeed(AbstractEngine engine);
>

Все двигатели, в свою очередь, будут содержать один параметр — максимальную скорость. Эта простая общедоступная переменная позволит сократить объем программы в данном примере:

abstract class AbstractEngine
<
public int max_speed;
>

Реализуем класс для автомобиля BMW:

class BMWCar : AbstractCar
<
public override void MaxSpeed(AbstractEngine engine)
<
Console.WriteLine(«Макcимальная скорость: « + engine.max_speed.ToString());
>
>

А затем определяем параметры его двигателя:

class BMWEngine : AbstractEngine
<
public BMWEngine()
<
max_speed = 200;
>
>

Проделаем то же самое для класса, описывающего автомобиль Audi:

class AudiCar : AbstractCar
<
public override void MaxSpeed(AbstractEngine engine)
<
Console.WriteLine(«Макcимальная скорость: « + engine.max_speed.ToString());
>
>

Задаем двигатель для него:

class AudiEngine : AbstractEngine
<
public AudiEngine()
<
max_speed = 180;
>
>

Теперь мы создадим класс Client, где покажем, как осуществляется работа с абстрактной фабрикой. В конструктор такого класса будут передаваться все конкретные фабрики, которые и начнут создавать объекты автомобиль и двигатель. Следовательно, в конструктор класса Client допустимо передать любую конкретную фабрику, работающую с любыми марками автомобилей. А метод Run позволит узнать максимальную скорость конкретной машины.

class Client
<
private AbstractCar abstractCar;
private AbstractEngine abstractEngine;
public Client(CarFactory car_factory)
<
abstractCar = car_factory.CreateCar();
abstractEngine = car_factory.CreateEngine ();
>
public void Run()
<
abstractCar.MaxSpeed(abstractEngine);
>
>

Ниже показано, как вызвать метод Run с различными параметрами:

public static void Main()
<
// Абстрактная фабрика № 1
CarFactory bmw_car = new BMWFactory ();
Client c1 = new Client(bmw_car);
c1.Run();
// Абстрактная фабрика № 2
CarFactory audi_factory = new AudiFactory();
Client c2 = new Client(audi_factory);
c2.Run();
Console.Read();
>

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

Шаблоны проектирования простым языком. Часть первая. Порождающие шаблоны

    Переводы, 1 июня 2017 в 12:14

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

Википедия описывает их следующим образом:

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

Будьте осторожны

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

Также заметьте, что примеры ниже написаны на PHP 7. Но это не должно вас останавливать, ведь принципы остаются такими же.

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

Шаблоны бывают следующих трех видов:

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

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

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

Простая фабрика (Simple Factory)

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

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

Читать еще:  2 системы программирования

Простыми словами: Простая фабрика генерирует экземпляр для клиента, не раскрывая никакой логики.

Перейдем к коду. У нас есть интерфейс Door и его реализация:

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

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

Когда использовать: Когда создание объекта — это не просто несколько присвоений, а какая-то логика, тогда имеет смысл создать отдельную фабрику вместо повторения одного и того же кода повсюду.

Фабричный метод (Fabric Method)

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

Пример из жизни: Рассмотрим пример с менеджером по найму. Невозможно одному человеку провести собеседования со всеми кандидатами на все вакансии. В зависимости от вакансии он должен распределить этапы собеседования между разными людьми.

Простыми словами: Менеджер предоставляет способ делегирования логики создания экземпляра дочерним классам.

«СМ-Клиника», Санкт-Петербург, 93 000 ₽

Перейдём к коду. Рассмотрим приведенный выше пример про HR-менеджера. Изначально у нас есть интерфейс Interviewer и несколько реализаций для него:

Теперь создадим нашего HiringManager :

И теперь любой дочерний класс может расширять его и предоставлять необходимого интервьюера:

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

Абстрактная фабрика (Abstract Factory)

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

Пример из жизни: Расширим наш пример про двери из простой фабрики. В зависимости от ваших нужд вам понадобится деревянная дверь из одного магазина, железная дверь — из другого или пластиковая — из третьего. Кроме того, вам понадобится соответствующий специалист: столяр для деревянной двери, сварщик для железной двери и так далее. Как вы можете заметить, тут есть зависимость между дверьми.

Простыми словами: Фабрика фабрик. Фабрика, которая группирует индивидуальные, но связанные/зависимые фабрики без указания их конкретных классов.

Обратимся к коду. Используем пример про двери. Сначала у нас есть интерфейс Door и несколько его реализаций:

Затем у нас есть несколько DoorFittingExpert для каждого типа дверей:

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

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

Когда использовать: Когда есть взаимосвязанные зависимости с не очень простой логикой создания.

Строитель (Builder)

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

Пример из жизни: Представьте, что вы пришли в McDonalds и заказали конкретный продукт, например, БигМак, и вам готовят его без лишних вопросов. Это пример простой фабрики. Но есть случаи, когда логика создания может включать в себя больше шагов. Например, вы хотите индивидуальный сэндвич в Subway: у вас есть несколько вариантов того, как он будет сделан. Какой хлеб вы хотите? Какие соусы использовать? Какой сыр? В таких случаях на помощь приходит шаблон «Строитель».

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

Давайте я покажу на примере, что такое «Телескопический конструктор». Когда-то мы все видели конструктор вроде такого:

Как вы можете заметить, количество параметров конструктора может резко увеличиться, и станет сложно понимать расположение параметров. Кроме того, этот список параметров будет продолжать расти, если вы захотите добавить новые варианты. Это и есть «Телескопический конструктор».

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

Затем мы берём «Строителя»:

Когда использовать: Когда может быть несколько видов объекта и надо избежать «телескопического конструктора». Главное отличие от «фабрики» — это то, что она используется, когда создание занимает один шаг, а «строитель» применяется при множестве шагов.

Прототип (Prototype)

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

Пример из жизни: Помните Долли? Овечка, которая была клонирована. Не будем углубляться, главное — это то, что здесь все вращается вокруг клонирования.

Простыми словами: Прототип создает объект, основанный на существующем объекте при помощи клонирования.

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

Обратимся к коду. В PHP это может быть легко реализовано с использованием clone :

Затем он может быть клонирован следующим образом:

Также вы можете использовать волшебный метод __clone для изменения клонирующего поведения.

Когда использовать: Когда необходим объект, похожий на существующий объект, либо когда создание будет дороже клонирования.

Одиночка (Singleton)

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

Пример из жизни: В стране одновременно может быть только один президент. Один и тот же президент должен действовать, когда того требуют обстоятельства. Президент здесь является одиночкой.

Простыми словами: Обеспечивает тот факт, что создаваемый объект является единственным объектом своего класса.

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

Прим. перев. Подробнее о подводных камнях шаблона одиночка читайте в нашей статье.

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

Паттерны проектирования

Что такое паттерны

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

Читать еще:  Структура языка программирования

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

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

Классификация паттернов

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

Паттерны делятся на следующие категории:

  • Архитектурные паттерны
  • Паттерны проектирования
  • Идиомы

Архитектурные паттерны, являясь наиболее высокоуровневыми паттернами, описывают структурную схему программной системы в целом. В данной схеме указываются отдельные функциональные составляющие системы, называемые подсистемами, а также взаимоотношения между ними. Примером архитектурного паттерна является хорошо известная программная парадигма «модель-представление-контроллер» (model-view-controller — MVC). В свою очередь, подсистемы могут состоять из архитектурных единиц уровнем ниже.

Паттерны проектирования описывают схемы детализации программных подсистем и отношений между ними, при этом они не влияют на структуру программной системы в целом и сохраняют независимость от реализации языка программирования. Паттерны GoF относятся именно к этой категории. Под паттернами проектирования объектно-ориентированных систем понимается описание взаимодействия объектов и классов, адаптированных для решения общей задачи проектирования в конкретном контексте. В русскоязычной литературе обычно встречаются несколько вариантов перевода оригинального названия design patterns — паттерны проектирования, шаблоны проектирования, образцы. Здесь в основном используется первый вариант, иногда второй.

Идиомы, являясь низкоуровневыми паттернами, имеют дело с вопросами реализации какой-либо проблемы с учетом особенностей данного языка программирования. При этом часто одни и те же идиомы для разных языков программирования выглядят по-разному или не имеют смысла вовсе. Например, в C++ для устранения возможных утечек памяти могут использоваться интеллектуальные указатели. Интеллектуальный указатель содержит указатель на участок динамически выделенной памяти, который будет автоматически освобожден при выходе из зоны видимости. В среде Java такой проблемы просто не существует, так как там используется автоматическая сборка мусора. Обычно, для использования идиом нужно глубоко знать особенности применяемого языка программирования. Следует отметить, что в программной области существуют и другие виды паттернов, не относящиеся к проектированию вообще, например, паттерны анализа, тестирования, документирования и др.

Programming stuff

Страницы

понедельник, 7 октября 2013 г.

Статьи

После наведения порядка с книгами пришло время выделить все статьи в отдельную заметку и сделать такую себе карту блога.

Ниже приведены наиболее значимые и интересные статьи, разбитые по разным тематикам, типа “Проектирования по контракту” или “C# Tips and Tricks”. При этом статьи представлены в порядке, наиболее удобном для изучения соответствующей темы, а не в хронологическом порядке. Наиболее значимые статьи, с моей точки зрения, выделены жирным.

Содержание

Дизайн и проектирование

Несмотря на любовь к языкам программирования навык проектирования я считаю несколько более важным навыком разработчика, поэтому именно с этой рубрики я бы хотел начать список статей.

Проектирование по контракту

Небольшой цикл статей о проектировании по контракту. Примеры приводятся для платформы .NET с использованию Code Contracts, понимание принципа проектирования по контракту будет полезным даже без использования инструментов.

  • Проектирование по контракту. Корректность ПО (07/05/2010)
  • Проектирование по контракту. Утверждения (11/05/2010)
  • Утверждения и защитное программирование (14/05/2010)
  • Проектирование по контракту. Наследование (17/05/2010)
  • Мониторинг утверждений в период выполнения (20/05/2010)

Другие статьи по контрактному программированию.

Принципы и паттерны проектирования

В этом разделе собраны статьи, которые легли в основу моей книги “Паттерны проектирования в .NET”, которая должна уже выйти в ближайшее время.

Принципы проектирования

  • Single Responsibility Principle (13/08/2014)
  • Open/Closed Principle (26/08/2014)
  • Open/Closed Princple. ФП vs. ООП (2/09/2014)
  • Liskov Substitution Principle (9/09/2014)
  • Interface Segregation Principle (14/08/2014)
  • The Dependency Inversion Principle (25/09/2014)
  • Шпаргалка по SOLID принципам(9/10/2014). Заключительная статья серии постов о принципах проектирования. В ней кратко рассматривается каждый принцип, а в конце приводится список anti-принципов.
  • DI vs. DIP vs. IoC (27/11/2014). Рассматривается разница между тремя схожими, на первый взгляд, понятиями, но смысл которых очень сильно отличается друг от друга.

Паттерны проектирования

  • GoF-паттерны на платформе .NET (21/02/2014). Вводная статья, в которой рассматриваются причины, побудившие меня рассматривать столь заезженную тему, как паттерны проектирования.
  • Паттерн Стратегия (26/02/2014)
  • Паттерн Шаблонный Метод (4/03/2014)
  • RAII в C#. Локальный метод шаблона (12/03/2014)
  • Паттерн Посредник(20/03/2014)
Управление зависимостями

Цикл статей об управлению зависимостями, но не столько в контексте инверсии управления (IoC), сколько с точки зрения дизайна.

  • Управление зависимостями (21/11/2012)
  • Фреймворки, библиотеки и зависимости (26/10/2012)
  • Аксиома управления зависимостями(28/02/2013)
  • Наследование vs. Композиция vs. Агрегация (03/12/2012)
  • Критический взгляд на принцип инверсии зависимостей(09/04/2013)
  • Инверсия зависимости на практике(23/01/2013)
  • Тестируемый дизайн vs. Хороший дизайн(26/04/2013). Сейчас очень часто смешивают понятия тестируемости и хорошего дизайна. Да, хороший дизайн является тестируемым, но обратное утверждение – не верно.
  • Пример тестируемого дизайна(13/05/2013). Очень часто спрашивают, как добиться тестируемого дизайна без выделения интерфейсов, в статье рассматривается пример такого дизайна.
  • DI Паттерны. Construction Injection (17/12/2012)
  • DI Паттерны. Property Injection (14/01/2013)
  • DI Паттерны. Method Injection (12/02/2013)
  • DI Паттерны. Service Locator(25/03/2013)
Дизайн (Core)

Раздел с философскими статьями о дизайне, борьбе со сложностью, тестируемости и подобными темами.

  • Ключевые концепции DDD (4/02/2014). Статья-рецензия знаменитой книги Эрика Эванса, в которой рассматриваются базовые понятия DDD.
  • Критерии плохого дизайна(29/01/2013). Почему дизайн большинства систем скатывается в непонятно что, какие характеристики дизайна являются ключевыми?
Юнит тестирование

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

  • Об автоматизированном тестировании(10/04/2014). Базовая статья о ключевых аспектах юнит-тестирования.
  • Модульный тест: определение (13/05/2014). Перевод одноименной статьи Мартина Фаулера.
  • Что значат для вас юнит-тесты(05/04/2012). У всех разное представление о том, для чего нужны юнит тесты. В этой статье я рассказываю о своем видении важности и нужности юнит тестирования.
  • Пример тестируемого дизайна (13/05/2013)
  • Как тестировать закрытые методы (08/05/2013)

Философия программирования

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

Философия программирования (разное)
  • Как проводить технические собеседования (26/06/2014)
  • Контракты vs. Монады? (30/06/2014)
  • Для чего нужны интерфейсы (14/12/2014)
  • Интервью с Бертраном Мейером (19/05/2014)
  • Кому нужен архитектор(11/11/2013).
  • Увлеченный программист(17/10/2013). Заходите сюда, как понадобится поднять свою мотивацию.
Паттерны поведения

Цикл статей о том, какие типовые “баги” в поведении людей можно встретить в нашей с вами работе.

C# Tips and Tricks

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

Устройство итераторов
Асинхронное программирование
  • Асинхронные операции и AsyncEnumerator (24/10/2010)
  • «Реактивные расширения» и асинхронные операции (25/11/2010) (RSDN Mag)
  • Знакомство с асинхронными операциями в C# 5 (02/12/2010) (RSDN Mag)
Многопоточное программирование от Джо Албахари
Исключения
  • Повторная генерация исключений (03/11/2011)
  • Гарантии безопасности исключений (15/08/2011)
  • Принцип самурая(13/09/2011)
Сборка мусора/управление ресурсами

  • О сборке мусора и достижимости объектов(27/08/2013)
  • Слабые ссылки в .NET (19/08/2013)
  • Немного о сборке мусора и поколениях(16/10/2012)
  • Источники о сборке мусора в .NET (11/10/2012)
  • Dispose Pattern (27/09/2011)
  • О явном вызове метода Dispose (12/08/2013)

Паттерн проектирования «Спецификация» в .NET

Сталкиваясь с DDD парадигмой вы точно столкнетесь с спецификацией. Спецификация часто используется вместе с паттерном «Репозиторий». Паттерн «спецификация» предоставляет возможность описывать требования к бизнес-объектам, и затем использовать их (и их композиции) для фильтрации не дублируя запросы.

в Википедии этот паттерн описан так:

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

Изучив несколько статей про спецификацию с других источников:

я решил объединить информацию с нескольких источников и описать полноценно этот паттерн в этой статье.

Начнем с того что рассмотрим UML схему классического шаблона спецификация:

Классическая реализация спецификации в .NET будет выглядеть следующим образом:

С появлением Generic’ов код можно переписать проще:

Перейдем от абстракций к более-менее реальному примеру.

Допустим, у нас есть портал который хранит в себе список фильмов и отдает их по определенному запросу:

Давайте представим, что пользователям нужно будет получать фильмы по дате, рейтингу, жанру.

Для этого мы создадим IMovieRepository :

Теперь, когда мы захотим объединить все критерии поиска, код может усложниться, пока добавим еще один метод Find, который обработает все возможные критерии и вернет консолидированный результат:

Проблема возникает тогда, когда нам нужно делать проверку не только на этапе запроса к БД. но и в бизнес логике как тут:

Если на уровне БД нам так же нужно фильтровать фильмы, мы добавим такой метод:

Проблема этих двух примеров в том, что они нарушают DRY принцип, поскольку метод проверки того, что является детским фильмом теперь «расплывается» в 2 метода (а по факту даже в 2 слоя приложения DAL и BLL). В таких случаях нас может выручить паттерн «спецификация». Мы можем создать класс-спецификацию который будет проверять можно ли показывать этот фильм детям и выдавать нам результат:

Этот подход позволяет не только убрать дублирование кода в разных доменных областях, но и позволяет комбинировать спецификации. Это в свою очередь позволяет «Аплаить» сколько спецификаций, сколько нам нужно.

Три случая когда стоит использовать шаблон проектирования «спецификация»:

  1. Поиск данных в базе данных. Это поиск записей который соответствуют спецификации, которую мы имеем реализовали.
  2. Проверка объектов в памяти. Другими словами, проверка того, что объект, который мы извлекли из БД соответствует спецификации.
  3. Создание нового экземпляра, который соответствует критериям. Это полезно в тех случаях, когда вы не заботитесь о реальном содержании экземпляров, но все же должны иметь определенные атрибуты.

GenericSpecification

Создавая спецификацию. у может нас появится желание создать генерик который будет обслуживать нужную нам спецификацию, например:

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

Помните (!) Вынос проблемы на другой уровень абстракции не решает эту проблему.

Generic спецификация не помогает решить DRY диллему, а значит не сильно нам подходит, да и в целом GenericSpecification — это плохая практика тк сами по себе они бесполезны.

Строго-типизированные спецификации

Как мы можем исправить проблему которая у нас возникла выше?

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

Перепишем наш код:

Благодаря такому уровню абстракции, нам не нужно теперь следить за тем как мы создаем спецификации и за их экземплярами, так же это решает проблему дублирования. Спецификации легко поддаются таким операциям как And, Or, Not и тд, для этого нам нужно добавить соответствующий код, например для And операции:

Чего просто не возвращать IQueryable в репозитории?

Разве не проще позволить клиентам запрашивать те данные которые они хотят реализовав метод Find который возвращает IQueryable ?

и потом уже использовать в контроллере (или любом другом месте где вы юзаете этот репозиторий) нужный запрос

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

Вторым недостатком здесь является то, что мы открываем контроль над DAL напрямую в контроллер. Что является болезненной и плохой практикой в многоуровневой архитектуре. Стоит помнить что, реализация IQueryable в значительной степени зависит от того, какой «поставщик» LINQ используется за кулисами, поэтому клиентский код должен знать, что потенциально существуют запросы, которые не могут быть скомпилированы в SQL.

Так же этот код нарушает еще один принцип The Liskov Substitution Principle​ (LSP).

Extension method vs specification

Возникает вопрос: а зачем городить спецификацию, если можно просто создать Extension, например:

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

Спецификация так же отлично работает и в паре с экстеншн методами.

Например, если вы реализуете SoftDelete подход через Extension:

вы можете спокойно юзать Extension, который будет общим для каждого из DBSet’ов где нужна поддержка SoftDelete и использовать его в паре с конкретными спецификациями в конкретных сервисах (репозиториях).

Ссылка на основную публикацию
Adblock
detector