Даб работа по методам программирования. обновлено пронько. 1. Классы в C#. Модификаторы доступа. Поля, свойства, индексаторы
![]()
|
20. Шаблон проектирования Адаптер (Adapter). Назначение. Пример использования. Паттерн Адаптер (Adapter) предназначен для преобразования интерфейса одного класса в интерфейс другого. Благодаря реализации данного паттерна мы можем использовать вместе классы с несовместимыми интерфейсами. Когда надо использовать Адаптер? Когда необходимо использовать имеющийся класс, но его интерфейс не соответствует потребностям Когда надо использовать уже существующий класс совместно с другими классами, интерфейсы которых не совместимы Формальное определение паттерна на UML выглядит следующим образом: ![]() Формальное описание адаптера объектов на C# выглядит таким образом: class Client { public void Request(Target target) { target.Request(); } } // класс, к которому надо адаптировать другой класс class Target { public virtual void Request() {} } // Адаптер class Adapter : Target { private Adaptee adaptee = new Adaptee(); public override void Request() { adaptee.SpecificRequest(); } } // Адаптируемый класс class Adaptee { public void SpecificRequest() {} } Участники Target: представляет объекты, которые используются клиентом Client: использует объекты Target для реализации своих задач Adaptee: представляет адаптируемый класс, который мы хотели бы использовать у клиента вместо объектов Target Adapter: собственно адаптер, который позволяет работать с объектами Adaptee как с объектами Target. То есть клиент ничего не знает об Adaptee, он знает и использует только объекты Target. И благодаря адаптеру мы можем на клиенте использовать объекты Adaptee как Target Теперь разберем реальный пример. Допустим, у нас есть путешественник, который путешествует на машине. Но в какой-то момент ему приходится передвигаться по пескам пустыни, где он не может ехать на машине. Зато он может использовать для передвижения верблюда. Однако в классе путешественника использование класса верблюда не предусмотрено, поэтому нам надо использовать адаптер: class Program { static void Main(string[] args) { // путешественник Driver driver = new Driver(); // машина Auto auto = new Auto(); // отправляемся в путешествие driver.Travel(auto); // встретились пески, надо использовать верблюда Camel camel = new Camel(); // используем адаптер ITransport camelTransport = new CamelToTransportAdapter(camel); // продолжаем путь по пескам пустыни driver.Travel(camelTransport); Console.Read(); } } interface ITransport { void Drive(); } // класс машины class Auto : ITransport { public void Drive() { Console.WriteLine("Машина едет по дороге"); } } class Driver { public void Travel(ITransport transport) { transport.Drive(); } } // интерфейс животного interface IAnimal { void Move(); } // класс верблюда class Camel : IAnimal { public void Move() { Console.WriteLine("Верблюд идет по пескам пустыни"); } } // Адаптер от Camel к ITransport class CamelToTransportAdapter : ITransport { Camel camel; public CamelToTransportAdapter(Camel c) { camel = c; } public void Drive() { camel.Move(); } } И консоль выведет: Машина едет по дороге Верблюд идет по пескам пустыни В данном случае в качестве клиента применяется класс Driver, который использует объект ITransport. Адаптируемым является класс верблюда Camel, который нужно использовать в качестве объекта ITransport. И адптером служит класс CamelToTransportAdapter. Возможно, кому-то покажется надуманной проблема использования адаптеров особенно в данном случае, так как мы могли бы применить интерфейс ITransport к классу Camel и реализовать его метод Drive(). Однако, в данном случае может случиться дублирование функциональностей: интерфейс IAnimal имеет метод Move(), реализация которого в классе верблюда могла бы быть похожей на реализацию метода Drive() из интерфейса ITransport. Кроме того, нередко бывает, что классы спроектированы кем-то другим, и мы никак не можем на них повлиять. Мы только используем их. В результате чего адаптеры довольно широко распространены в .NET. В частности, многочисленные встроенные классы, которые используются для подключения к различным системам баз данных, как раз и реализуют паттерн адаптер (например, класс System.Data.SqlClient.SqlDataAdapter). 21. Шаблон проектирования Прокси (Proxy). Назначение. Пример использования. Паттерн Заместитель (Proxy) предоставляет объект-заместитель, который управляет доступом к другому объекту. То есть создается объект-суррогат, который может выступать в роли другого объекта и замещать его. Назначение: Когда надо осуществлять взаимодействие по сети, а объект-проси должен имитировать поведения объекта в другом адресном пространстве. Использование прокси позволяет снизить накладные издержки при передаче данных через сеть. Подобная ситуация еще называется удалённый заместитель (remote proxies). Когда нужно управлять доступом к ресурсу, создание которого требует больших затрат. Реальный объект создается только тогда, когда он действительно может понадобится, а до этого все запросы к нему обрабатывает прокси-объект. Подобная ситуация еще называется виртуальный заместитель (virtual proxies). Когда необходимо разграничить доступ к вызываемому объекту в зависимости от прав вызывающего объекта. Подобная ситуация еще называется защищающий заместитель (protection proxies). Когда нужно вести подсчет ссылок на объект или обеспечить потокобезопасную работу с реальным объектом. Подобная ситуация называется "умные ссылки" (smart reference). С помощью UML паттерн может быть описан так: ![]() Примериспользования: 1. class Client 2. { 3. void Main() 4. { 5. Subject subject = new Proxy(); 6. subject.Request(); 7. } 8. } 9. abstract class Subject 10. { 11. public abstract void Request(); 12. } 13. 14. class RealSubject : Subject 15. { 16. public override void Request() 17. {} 18. } 19. class Proxy : Subject 20. { 21. RealSubject realSubject; 22. public override void Request() 23. { 24. if (realSubject == null) 25. realSubject = new RealSubject(); 26. realSubject.Request(); 27. } 28. } Участники паттерна: Subject: определяет общий интерфейс для Proxy и RealSubject. Поэтому Proxy может использоваться вместо RealSubject. RealSubject: представляет реальный объект, для которого создается прокси. Proxy: заместитель реального объекта. Хранит ссылку на реальный объект, контролирует к нему доступ, может управлять его созданием и удалением. При необходимости Proxy переадресует запросы объекту RealSubject. Client: использует объект Proxy для доступа к объекту RealSubject. 22. Шаблон проектирования Декоратор (Decorator). Назначение. Пример использования. Декоратор (Decorator) представляет структурный шаблон проектирования, который позволяет динамически подключать к объекту дополнительную функциональность. Для определения нового функционала в классах нередко используется наследование. Декораторы же предоставляет наследованию более гибкую альтернативу, поскольку позволяют динамически в процессе выполнения определять новые возможности у объектов. Когда применение наследования неприемлемо. Например, если нам надо определить множество различных функциональностей и для каждой функциональности наследовать отдельный класс, то структура классов может очень сильно разрастись. Еще больше она может разрастись, если нам необходимо создать классы, реализующие все возможные сочетания добавляемых функциональностей. Схематически шаблон "Декоратор" можно выразить следующим образом: ![]()
Участники Component: абстрактный класс, который определяет интерфейс для наследуемых объектов ConcreteComponent: конкретная реализация компонента, в которую с помощью декоратора добавляется новая функциональность Decorator: собственно декоратор, реализуется в виде абстрактного класса и имеет тот же базовый класс, что и декорируемые объекты. Поэтому базовый класс Component должен быть по возможности легким и определять только базовый интерфейс. Класс декоратора также хранит ссылку на декорируемый объект в виде объекта базового класса Component и реализует связь с базовым классом как через наследование, так и через отношение агрегации. Классы ConcreteDecoratorA и ConcreteDecoratorB представляют дополнительные функциональности, которыми должен быть расширен объект ConcreteComponent. Рассмотрим пример. Допустим, у нас есть пиццерия, которая готовит различные типы пицц с различными добавками. Есть итальянская, болгарская пиццы. К ним могут добавляться помидоры, сыр и т.д. И в зависимости от типа пицц и комбинаций добавок пицца может иметь разную стоимость. Теперь посмотрим, как это изобразить в программе на C#:
В качестве компонента здесь выступает абстрактный класс Pizza, который определяет базовую функциональность в виде свойства Name и метода GetCost(). Эта функциональность реализуется двумя подклассами ItalianPizza и BulgerianPizza, в которых жестко закодированы название пиццы и ее цена. Декоратором является абстрактный класс PizzaDecorator, который унаследован от класса Pizza и содержит ссылку на декорируемый объект Pizza. В отличие от формальной схемы здесь установка декорируемого объекта происходит не в методе SetComponent, а в конструкторе. Отдельные функциональности - добавление томатов и сыры к пиццам реализованы через классы TomatoPizza и CheesePizza, которые обертывают объект Pizza и добавляют к его имени название добавки, а к цене - стоимость добавки, то есть переопределяя метод GetCost и изменяя значение свойства Name.
23. Шаблон проектирования Наблюдатель (Observer). Назначение. Пример использования. Паттерн "Наблюдатель" (Observer) представляет поведенческий шаблон проектирования, который использует отношение "один ко многим". В этом отношении есть один наблюдаемый объект и множество наблюдателей. И при изменении наблюдаемого объекта автоматически происходит оповещение всех наблюдателей. Когда использовать: Когда система состоит из множества классов, объекты которых должны находиться в согласованных состояниях Когда общая схема взаимодействия объектов предполагает две стороны: одна рассылает сообщения и является главным, другая получает сообщения и реагирует на них. Отделение логики обеих сторон позволяет их рассматривать независимо и использовать отдельно друга от друга. Когда существует один объект, рассылающий сообщения, и множество подписчиков, которые получают сообщения. При этом точное число подписчиков заранее неизвестно и процессе работы программы может изменяться. UML ![]() IObservable: представляет наблюдаемый объект. Определяет три метода: AddObserver() (для добавления наблюдателя), RemoveObserver() (удаление набюдателя) и NotifyObservers() (уведомление наблюдателей) ConcreteObservable: конкретная реализация интерфейса IObservable. Определяет коллекцию объектов наблюдателей. IObserver: представляет наблюдателя, который подписывается на все уведомления наблюдаемого объекта. Определяет метод Update(), который вызывается наблюдаемым объектом для уведомления наблюдателя. ConcreteObserver: конкретная реализация интерфейса IObserver. При этом наблюдаемому объекту не надо ничего знать о наблюдателе кроме того, что тот реализует метод Update(). С помощью отношения агрегации реализуется слабосвязанность обоих компонентов. Изменения в наблюдаемом объекте не виляют на наблюдателя и наоборот. В определенный момент наблюдатель может прекратить наблюдение. И после этого оба объекта - наблюдатель и наблюдаемый могут продолжать существовать в системе независимо друг от друга. Пример: 1. class Program 2. { 3. static void Main(string[] args) 4. { 5. Stock stock = new Stock(); 6. Bank bank = new Bank("ЮнитБанк", stock); 7. Broker broker = new Broker("Иван Иваныч", stock); 8. // имитация торгов 9. stock.Market(); 10. // брокер прекращает наблюдать за торгами 11. broker.StopTrade(); 12. // имитация торгов 13. stock.Market(); 14. 15. Console.Read(); 16. } 17. } 18. 19. interface IObserver 20. { 21. void Update(Object ob); 22. } 23. 24. interface IObservable 25. { 26. void RegisterObserver(IObserver o); 27. void RemoveObserver(IObserver o); 28. void NotifyObservers(); 29. } 30. 31. class Stock : IObservable 32. { 33. StockInfo sInfo; // информация о торгах 34. 35. List 36. public Stock() 37. { 38. observers = new List 39. sInfo= new StockInfo(); 40. } 41. public void RegisterObserver(IObserver o) 42. { 43. observers.Add(o); 44. } 45. 46. public void RemoveObserver(IObserver o) 47. { 48. observers.Remove(o); 49. } 50. 51. public void NotifyObservers() 52. { 53. foreach(IObserver o in observers) 54. { 55. o.Update(sInfo); 56. } 57. } 58. 59. public void Market() 60. { 61. Random rnd = new Random(); 62. sInfo.USD = rnd.Next(20, 40); 63. sInfo.Euro = rnd.Next(30, 50); 64. NotifyObservers(); 65. } 66. } 67. 68. class StockInfo 69. { 70. public int USD { get; set; } 71. public int Euro { get; set; } 72. } 73. 74. class Broker : IObserver 75. { 76. public string Name { get; set; } 77. IObservable stock; 78. public Broker(string name, IObservable obs) 79. { 80. this.Name = name; 81. stock = obs; 82. stock.RegisterObserver(this); 83. } 84. public void Update(object ob) 85. { 86. StockInfo sInfo = (StockInfo)ob; 87. 88. if(sInfo.USD>30) 89. Console.WriteLine("Брокер {0} продает доллары; Курс доллара: {1}", this.Name, sInfo.USD); 90. else 91. Console.WriteLine("Брокер {0} покупает доллары; Курс доллара: {1}", this.Name, sInfo.USD); 92. } 93. public void StopTrade() 94. { 95. stock.RemoveObserver(this); 96. stock=null; 97. } 98. } 99. 100. class Bank : IObserver 101. { 102. public string Name { get; set; } 103. IObservable stock; 104. public Bank(string name, IObservable obs) 105. { 106. this.Name = name; 107. stock = obs; 108. stock.RegisterObserver(this); 109. } 110. public void Update(object ob) 111. { 112. StockInfo sInfo = (StockInfo)ob; 113. 114. if (sInfo.Euro > 40) 115. Console.WriteLine("Банк {0} продает евро; Курс евро: {1}", this.Name, sInfo.Euro); 116. else 117. Console.WriteLine("Банк {0} покупает евро; Курс евро: {1}", this.Name, sInfo.Euro); 118. } 119. } Итак, здесь наблюдаемый объект представлен интерфейсом IObservable, а наблюдатель - интерфейсом IObserver. Реализацией интерфейса IObservable является класс Stock, который символизирует валютную биржу. В этом классе определен метод Market(), который имитирует торги и инкапсулирует всю информацию о валютных курсах в объекте StockInfo. После проведения торгов производится уведомление всех наблюдателей. Реализациями интерфейса IObserver являются классы Broker, представляющий брокера, и Bank, представляющий банк. При этом метод Update() интерфейса IObserver принимает в качестве параметра некоторый объект. Реализация этого метода подразумевает получение через данный параметр объекта StockInfo с текущей информацией о торгах и произведение некоторых действий: покупка или продажа долларов и евро. Дело в том, что часто необходимо информировать наблюдателя об изменении состояния наблюдаемого объекта. В данном случае состояние заключено в объекте StockInfo. И одним из вариантом информирования наблюдателя о состоянии является push-модель, при которой наблюдаемый объект передает (иначе говоря толкает - push) данные о своем состоянии, то есть передаем в виде параметра метода Update(). |