Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

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

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

Кажется, это определение мы слышали 1000 раз… Помимо познания терминов и паттернов увлекательно знать, как они используются в реальных планах.

В статье я разгляжу несколько особенно знаменитых паттернов используемых в .NET. Некоторые из них велико интегрированы в инфраструктуру .NET, в то время как другие легко используются при проектировании базовых классов в BCL.

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

Мы разглядим следующие шесть паттернов:

  1. наблюдатель;
  2. итератор;
  3. декоратор;
  4. адаптер;
  5. фабрика;
  6. тактика.

Выходит, начнем.

Наблюдатель

Вероятно, особенно знаменитый паттерн, тот, что применен в .NET — это наблюдатель. Знаменит он потому, что заложен в основу делегатов, которые являются неотделимой частью .NET с самого его возникновения. Трудно не оценить их взнос в объектную модель .NET, исключительно рассматривая то, во что они вылились в больше поздних версиях (лямбды, замыкания, и т.д.).

Предназначение паттерна наблюдатель из GOF: определяет связанность типа «один ко многим» между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом и механически обновляются.

Простейшая реализация данного паттерна на языке C# может выглядеть приблизительно так:

Легкой наблюдатель

public interface IObserver // Интерфейс наблюдателя
{
    void Notify();
}

public abstract class Subject // Субъект
{
  private List<IObserver> observers = new List<IObserver>();

  public void Add(IObserver o)
  {
     observers.Add(o);
  }

  public void Remove(IObserver o)
  {
    observers.Remove(o);
  }

  public void Notify() // Оповестить всех наблюдателей
  {
    foreach (IObserver o in observers)
        o.Notify();
  }
}

Как я теснее сказал в платформе .NET абстракцию наблюдатель реализуют делегаты. Для больше комфортной работы с делегатами в C# применяются события. В дальнейшем примере будем их применять:

Наблюдатель в .NET

public delegate void MyEventHandler(); // Пользовательский тип делегата

public class Subject
{
  public Subject() { }
  public MyEventHandler MyEvent; // Событие

  public void RaiseEvent() // Вызвать все способы ассоциированные с событием
   {
      MyEventHandler ev = MyEvent;
        if (ev != null)
         ev();
   }
}

static void Main(string[] args)
{
   Subject s = new Subject();
   s.MyEvent  = () => Console.WriteLine("Hello habrahabr"); // присоединить делегат
   s.MyEvent  = () => Console.WriteLine("Hello world"); // присоединить делегат
   s.RaiseEvent(); // вызвать все делегаты
   Console.ReadKey();
}

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

На заметку

У кода описанного выше есть один недочет: при применении лямбда выражений не получится отсоединить делегат, то есть код

s.MyEvent -= () => Console.WriteLine("Hello habrahabr");

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

static void Main(string[] args)
{
  Subject s = new Subject();
  MyEventHandler myEv1 = () => Console.WriteLine("Hello habarhabr");
  MyEventHandler myEv2 = () => Console.WriteLine("Hello world");
  s.MyEvent  = myEv1;
  s.MyEvent  = myEv2;
  s.MyEvent -= myEv1;

  s.RaiseEvent(); // будет вызван только один способ
  Console.ReadKey();
}

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

public interface IObservable<out T>
{
   IDisposable Subscribe(IObserver<T> observer);
}

public interface IObserver<in T>
{
  void OnCompleted();
  void OnError(Exception error);
  void OnNext(T value);
}

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

На заметку

Итератор

Дальнейший не менее знаменитый по применению в .NET паттерн — итератор.

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

На платформе .NET за реализацию паттерна итератор отвечают два интерфейса: IEnumerable и IEnumerator

public interface IEnumerable
{
   IEnumerator GetEnumerator();
}

public interface IEnumerator
{
  object Current { get; }
  bool MoveNext();
  void Reset();
}

а так же их обобщенные аналоги:

public interface IEnumerable<out T> : IEnumerable
{
 IEnumerator<T> GetEnumerator();
}

public interface IEnumerator<out T> : IDisposable, IEnumerator
{
  T Current { get; }
}

Для того Дабы иметь вероятность итерировать некоторую сущность с поддержкой цикла foreach в ней нужно реализовать интерфейс IEnumerable<>, а так же сделать сам итератор — класс /структуру реализующую интерфейс IEnumerator<>.

С учетом того, что в C# 2.0 возникли блоки итераторов, применяя ключевое слово yield, создание пользовательских типов реализующих интерфейс IEnumerator<> сейчас появляется редко (компилятор делает это за нас).

Цикл foreach работает бок о бок с паттерном итератор. Дальнейший код

foreach (var item in Enumerable.Range(0, 10))
{
  Console.WriteLine(item);
}

это легко синтаксический сахар для кода:

IEnumerator<int> enumerator = Enumerable.Range(0, 10).GetEnumerator();
try
 {
   while (enumerator.MoveNext())
   {
     int item = enumerator.Current;
     Console.WriteLine(item);
   }
 }
finally
 {
  if (enumerator != null)
  {
   enumerator.Dispose();
  }
}

Таким образом, паттерн итератор заложен в основу языка C#, от того что его языковая конструкция (foreach) использует его.

На заметку

О том, что цикл foreach на самом деле не требует, Дабы итерируемая сущность реализовывала интерфейс IEnumerable (а требует лишь присутствие определенных способов с заданными сигнатурами) писали многие, следственно я говорить об этом не буду. У Сергея Теплякова SergeyT есть отличный пост, посвященный именно работе цикла foreach.

В .NET существуют несколько оптимизаций, касающихся цикла foreach. От того что при всякой итерации создается объект итератор, то это может отрицательно сказаться на сборке мусора, исключительно если будет несколько вложенных циклов foreach, следственно при итерировании массивов и строк (типов велико интегрированных в CLR) цикл foreach разворачивается в обыкновенный цикл for.

На заметку

Декоратор

Паттерн №3 — декоратор объектов.

Предназначение паттерна итератор из GOF: динамически добавляет объекту новые обязанности. Является эластичной альтернативой порождению подклассов с целью растяжения функциональности.

Абстракцию декоратор в .NET представляет класс System.IO.Stream и его преемники. Разглядим дальнейший код:

public static void WriteBytes(Stream stream)
{
  int oneByte;
   while ((oneByte = stream.ReadByte()) >= 0)
    {
      Console.WriteLine(oneByte);
    }
}

static void Main(string[] args)
{
  var memStream = new MemoryStream(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
  WriteBytes(memStream);

  var buffStream = new BufferedStream(memStream);
  WriteBytes(buffStream);

  Console.ReadLine();
}

Способ WriteBytes работает с любым потоком, но делает это не неизменно результативно. Читать с диска по одному байту не дюже отлично, следственно мы можем применять класс BufferedStream, тот, что считывает данные блоком, а потом стремительно их возвращает. Класс BufferedStream в конструкторе принимает тип Stream (всякий поток), тем самым оборачивая (декорируя) его в больше результативную оболочку. BufferedStream переопределяет основные способы класса Stream, такие как Read и Write, Дабы обеспечить больше широкую функциональность.

Класс System.Security.Cryptography.CryptoStream разрешает шифровать и расшифровывать потоки на лету. Он так же принимает в конструкторе параметр с типом Stream, оборачивая его в свою оболочку.

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

Адаптер

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

COM и .NET имеют разную внутреннюю архитектуру. Следственно нужно бывает адаптировать один интерфейс к иному. Среда CLR предоставляет доступ к COM-объектам через посредник, называемый вызываемой оболочкой времени выполнения (RCW).

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

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

Скажем, когда заказчик .NET передает в неуправляемый объект как часть довода тип String, оболочка преобразует эту строку в тип BSTR (пост, в котором я описывал особенности строк в .NET). Если COM-объект возвращает управляемому вызывающему объекту данные типа BSTR, дерзкий объект получит данные типа String. И заказчик, и сервер отправляют и получают данные в внятном им представлении.

Реально, RCW — это адаптер, преобразующий один интерфейс к иному.

Фабрика

Пятый паттерн — это своего рода фабрика.

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

Класс System.Convert содержит комплект статических способов, которые разрешают создавать объекты без очевидного вызова их конструкторов, и работает как фабрика. Для реформирования целого числа в логическое, скажем, мы можем вызвать и передать в способ Convert.ToBoolean в качестве параметра целое число. Возвращаемое значение этого способа будет правда, если число было ненулевым и вранье в отвратном случае. Другие способы реформирования типов работают подобно.

Такая тактика для создания новых экземпляров объектов знаменита как образец Фабрика. Мы можем не вызывая конструктора попросить у фабрики сделать необходимый объект. Таким образом, паттерн Фабрика может спрятать трудность создания объекта. Если мы хотим изменить детали создания объекта, необходимо каждого лишь изменить саму фабрику, нам не придется менять всякое место в коде, в котором вызывается конструктор.

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

Тактика

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

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

public interface IComparable<T>
{
  int CompareTo(T other);
}

Код сортировки реально не зависит от алгорифмов сопоставления элементов и может оставаться постоянным.

Сюда так же дозволено отнести способы Array.BinarySearch, тот, что так же принимает интерфейс IComparable и способ Array.Find, тот, что принимает делегат Predicate. Тем самым, варьируя разные делегаты (стратегии) мы можем менять поведение способа и получать нужный нам итог.

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

Завершение

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

Спасибо за прочтение. Верю, статья оказалась пригодной.

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB