Цепочка обязанностей (Chain of responsibility) 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Цепочка обязанностей (Chain of responsibility)



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

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

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

 

Рис. 18. UML-диаграмма шаблона Цепочка обязанностей.

Ниже приведён код, иллюстрирующий применение шаблона Цепочка обязанностей.

using System;

 

public class Handler

{

private readonly Handler next;

private readonly int id;

 

public int Limit { get; private set; }

 

public Handler(int id, Handler handler)

{

this.id = id;

Limit = id * 1000;

next = handler;

}

 

public string HandleRequest(int data)

{

if (data < Limit)

{

return string.Format(

"Request for {0} handled at level {1}", data, id);

}

return next!= null

? next.HandleRequest(data)

: string.Format(

"Request for {0} handled BY DEFAULT at level {1}", data, id);

}

}

 

public class ChainOfResponsibilityExample

{

private static void Main()

{

Handler start = null;

for (var i = 5; i > 0; i--)

{

Console.WriteLine(

"Handler {0} deals up to a limit of {1}", i, i * 1000);

start = new Handler(i, start);

}

int[] a = { 50, 2000, 1500, 10000, 175, 4500 };

foreach (var i in a)

{

Console.WriteLine(start.HandleRequest(i));

}

}

}

Команда (Command)

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

Классической иллюстрацией применения шаблона Команда является система меню любого оконного приложения. Пункты меню – это как раз и есть те команды, которые описывает шаблон. Некоторые команды в определенный момент времени доступны для выполнения, другие – нет. Заданный набор команд поддерживает функции отмены и повторного выполнения (Undo и Redo). Также обычно существует способ объединить несколько команд в одну макрокоманду.

Дизайн шаблона Команда показан на рис. 19.

 

Рис. 19. Дизайн шаблона Команда.

Объект Client выражает необходимость в определенных операциях в виде высокоуровневых терминов, зависящих от предметной области. Объекты Receiver знают, как выполнить требуемую операцию. Класс Command формирует интерфейс между Client и Receiver. В объекте-команде клиентские запросы ассоциируются с доступными операциями в объектах Receiver. Возможно, что клиент передает объектам Receiver некоторый контекст выполнения (или своё состояние).

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

using System;

 

public delegate void Invoker();

 

public static class InvokerExtensions

{

private static int count;

 

public static int Count(this Invoker invoker)

{

return count;

}

 

public static void Log(this Invoker invoker)

{

count++;

}

}

 

public abstract class ICommand

{

public Invoker Execute, Redo, Undo;

}

 

public class CommandMenu

{

private class Paste: ICommand

{

public Paste(Document document)

{

Execute = delegate

{

Execute.Log();

document.Paste();

};

Redo = delegate

{

Redo.Log();

document.Paste();

};

Undo = delegate

{

Undo.Log();

document.Restore();

};

}

}

 

private class Print: ICommand

{

public Print(Document document)

{

Execute = delegate

{

Execute.Log();

document.Print();

};

Redo = delegate

{

Redo.Log();

document.Print();

};

Undo = delegate

{

Undo.Log();

Console.WriteLine("Cannot undo");

};

}

}

 

private static string clipboard;

 

private class Document

{

private readonly string name;

private string oldpage, page;

 

public Document(string name)

{

this.name = name;

}

 

public void Paste()

{

oldpage = page;

page += clipboard + "\n";

}

 

public void Restore()

{

page = oldpage;

}

 

public void Print()

{

Console.WriteLine("File " + name + " at " +

DateTime.Now + "\n" + page);

}

}

 

private static void Main()

{

var document = new Document("Greetings");

var paste = new Paste(document);

var print = new Print(document);

clipboard = "Hello, everyone";

paste.Execute();

print.Execute();

paste.Undo();

clipboard = "Bonjour, mes amis";

paste.Execute();

clipboard = "Guten morgen, meine Freunde";

paste.Redo();

print.Execute();

print.Undo();

Console.WriteLine("Logged " + paste.Execute.Count() +

" commands");

}

}

3.10. ШАБЛОНЫ ПОВЕДЕНИЯ: ИТЕРАТОР, ПОСРЕДНИК, НАБЛЮДАТЕЛЬ

Итератор (Iterator)

Назначение шаблона Итератор – предоставить последовательный доступ к элементам коллекции без информации о её внутреннем устройстве. Дополнительно шаблон может реализовывать функционал по фильтрации элементов коллекции.

Концепция итераторов и перечислителей (также называемых генераторы) не является новой. Перечислители ответственны за предоставление следующего элемента коллекции, удовлетворяющего определенному критерию. В последние годы возможности создания итераторов и перечислителей были интегрированы в синтаксис многих языков программирования, что позволило упростить код и сделать реализацию шаблонов компактной. Диаграмма шаблона Итератор использует имена, применяемые в.NET Framework и языке C#.

 

Рис. 19. Шаблон Итератор.

При практической реализации шаблона Итератор на C#, безусловно, используются такие возможности, как перечислители, интерфейсы IEnumerable и IEnumerator, оператор yield. В качестве примера использования итераторов рассмотрим работу со структурой данных, соответствующей семейному дереву (рис. 20).

 

Рис. 20. Семейное дерево.

using System;

using System.Collections;

using System.Collections.Generic;

 

public class Tree<T>: IEnumerable<T>

{

public T Root { get; private set; }

public IList<Tree<T>> Leaves { get; private set; }

 

public Tree(T root, params Tree<T>[] leaves)

{

Root = root;

Leaves = Array.AsReadOnly(leaves);

}

 

public IEnumerable<T> Where(Func<T, bool> filter)

{

foreach (var p in this)

{

if (filter(p))

{

yield return p;

}

}

}

 

public IEnumerator<T> GetEnumerator()

{

yield return Root;

foreach (var leaf in Leaves)

{

foreach (var item in leaf)

{

yield return item;

}

}

}

 

IEnumerator IEnumerable.GetEnumerator()

{

return GetEnumerator();

}

}

 

public class Person

{

public string Name { get; set; }

public int Birth { get; set; }

 

public Person(string name, int birth)

{

Name = name;

Birth = birth;

}

 

public override string ToString()

{

return string.Format("[{0}, {1}]", Name, Birth);

}

}

 

public class TreeIteratorExample

{

private static void Main()

{

var family = new Tree<Person>(

new Person("Tom", 1950),

new Tree<Person>(new Person("Peter", 1976),

new Tree<Person>(new Person("James", 2000)),

new Tree<Person>(new Person("Sarah", 2002))

),

new Tree<Person>(new Person("Robert", 1978)),

new Tree<Person>(new Person("Mark", 1982)),

new Tree<Person>(new Person("Carrie", 2005))

);

foreach (var person in family.Where(p => p.Birth > 1980))

{

Console.WriteLine(person);

}

}

}

Посредник (Mediator)

Шаблон Посредник служит для обеспечения коммуникации между объектами. Этот шаблон также инкапсулирует протокол, которому должна удовлетворять процедура коммуникации.

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

 

Рис. 21. Дизайн шаблона Посредник.

Дизайн шаблона посредник предполагает наличие двух выделенных классов, использующих сообщения для взаимного обмена информацией: Colleague (коллега) и Mediator (посредник). Объект Colleague регистрирует объект Mediator и сохраняет его в своём внутреннем поле. При регистрации посреднику предоставляется функция обратного вызова Receive(). В свою очередь, посредник поддерживает список зарегистрировавших его объектов. Как только один из объектов Colleague вызывает свой метод Send(), посредник вызывает у остальных зарегистрированных объектов метод Receive(). При практической реализации шаблона Посредник в.NET Framework список зарегистрированных объектов можно поддерживать в виде словаря, а можно использовать для рассылки сообщений возможности групповых делегатов.

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

using System;

using System.Collections.Generic;

 

public class Mediator

{

public delegate void Callback(string message, string from);

 

private readonly HashSet<Colleague> colleagues =

new HashSet<Colleague>();

 

public void SignOn(Colleague colleague)

{

colleagues.Add(colleague);

}

 

// Посылка сообщения всем коллегам

public void Send(string message, string from)

{

foreach (var colleague in colleagues)

{

colleague.Receive(message, from);

}

}

}

 

public class Colleague

{

private readonly Mediator mediator;

protected string name;

 

public Colleague(Mediator mediator, string name)

{

this.mediator = mediator;

this.name = name;

mediator.SignOn(this);

}

 

public virtual void Receive(string message, string from)

{

Console.WriteLine("{0} received from {1}: {2}",

name, from, message);

}

 

public void Send(string message)

{

Console.WriteLine("Send (from {0}): {1}", name, message);

mediator.Send(message, name);

}

}

 

public class AnotherColleague: Colleague

{

public AnotherColleague(Mediator mediator, string name)

: base(mediator, name) { }

 

// Собственные сообщения игнорируем

public override void Receive(string message, string from)

{

if (!String.Equals(from, name))

{

Console.WriteLine("{0} received from {1}: {2}",

name, from, message);

}

}

}

 

public class MediatorExample

{

private static void Main()

{

var m = new Mediator();

var john = new Colleague(m, "John");

var lucy = new Colleague(m, "Lucy");

var david = new AnotherColleague(m, "David");

john.Send("Meeting on Tuesday, please all ack");

david.Send("Ack");

john.Send("Still awaiting some Acks");

lucy.Send("Ack");

john.Send("Thanks all");

}

}

Наблюдатель (Observer)

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

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

Как и в случае с шаблоном Посредник, дизайн шаблона Наблюдатель предполагает наличие двух выделенных классов. Объект класса Subject изменяет своё состояния, и именно эти изменения предполагается отслеживать. Объекты (их может быть несколько) класса Observer могут подписываться на отслеживание изменений. Класс Subject располагает закрытым событием с именем Notify. Как только Subject изменяет своё состояние, событие активируется. При этом вызывается метод Update() подписчиков, которому передаётся состояние объекта Subject. Метод Update() предварительно регистрируется в Subject при помощи операции Attach().

 

Рис. 22. Дизайн шаблона Наблюдатель.

Далее приводится пример кода, демонстрирующего реализацию шаблона Наблюдатель. Заметим, что в примере намеренно не используются события.

using System;

using System.Collections.Generic;

 

// аналог Subject

public abstract class Stock

{

private double price;

private readonly IList<IInvestor> investors =

new List<IInvestor>();

 

public double Price

{

get { return price; }

set

{

if (price!= value)

{

price = value;

Notify();

}

}

}

 

public string Symbol { get; private set; }

 

protected Stock(string symbol, double price)

{

Symbol = symbol;

this.price = price;

}

 

public void Attach(IInvestor investor)

{

investors.Add(investor);

}

 

public void Detach(IInvestor investor)

{

investors.Remove(investor);

}

 

public void Notify()

{

foreach (var investor in investors)

{

investor.Update(this);

}

}

}

 

public class IBM: Stock

{

public IBM(double price): base("IBM", price) { }

}

 

// интерфейс, аналогичный IObserver

public interface IInvestor

{

void Update(Stock stock);

}

 

// конкретный обозреватель

public class Investor: IInvestor

{

public string Name { get; private set; }

public Stock Stock { get; set; }

 

public Investor(string name)

{

Name = name;

}

 

public void Update(Stock stock)

{

Console.WriteLine("Notified {0} of {1}'s change to {2:C}",

Name, stock.Symbol, stock.Price);

}

}

 

public class ObserverExample

{

private static void Main()

{

var ibm = new IBM(120.00);

ibm.Attach(new Investor("Sorros"));

ibm.Attach(new Investor("Berkshire"));

ibm.Price = 120.10;

ibm.Price = 121.00;

ibm.Price = 120.50;

ibm.Price = 120.75;

Console.ReadKey();

}

}

3.11. ШАБЛОНЫ ПОВЕДЕНИЯ: ПОСЕТИТЕЛЬ, ИНТЕРПРЕТАТОР, ХРАНИТЕЛЬ

Посетитель (Visitor)

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

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

using System;

using System.Collections.Generic;

using System.IO;

 

public abstract class NodeBase

{

public string Name { get; private set; }

 

protected NodeBase(string name)

{

Name = name;

}

 

public abstract void Print(TextWriter writer);

}

 

public class SimpleNode: NodeBase

{

public SimpleNode(string name): base(name) { }

 

public override void Print(TextWriter writer)

{

writer.WriteLine(Name + ": Simple");

}

}

 

public class CompositeNode: NodeBase

{

public IList<SimpleNode> SimpleNodes { get; private set; }

 

public CompositeNode(string name, SimpleNode[] nodes):

base(name)

{

SimpleNodes = Array.AsReadOnly(nodes);

}

 

public override void Print(TextWriter writer)

{

writer.WriteLine(Name + ": Composite");

foreach (var node in SimpleNodes)

{

node.Print(writer);

}

}

}

 

public class RootNode: NodeBase

{

public IList<SimpleNode> SimpleNodes { get; private set; }

public IList<CompositeNode> CompositeNodes { get; private set; }

 

public RootNode(SimpleNode[] simpleNodes,

CompositeNode[] compositeNodes): base("Root")

{

SimpleNodes = Array.AsReadOnly(simpleNodes);

CompositeNodes = Array.AsReadOnly(compositeNodes);

}

 

public override void Print(TextWriter writer)

{

writer.WriteLine(Name + ": Root");

foreach (var node in SimpleNodes)

{

node.Print(writer);

}

foreach (var node in CompositeNodes)

{

node.Print(writer);

}

}

}

 

public class Program

{

private static NodeBase CreateTree()

{

return

new RootNode(

new[] { new SimpleNode("T11"),

new SimpleNode("T12"),

new SimpleNode("T13") },

new[]

{

new CompositeNode("T21",

new[] { new SimpleNode("T321"),

new SimpleNode("T322"), }),

new CompositeNode("T22",

new[] { new SimpleNode("T321"),

new SimpleNode("T322"), })

});

}

 

private static void Main()

{

var tree = CreateTree();

tree.Print(Console.Out);

}

}

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

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

public interface IVisitor

{

void VisitSimpleNode(SimpleNode node);

void VisitCompositeNode(CompositeNode node);

void VisitRootNode(RootNode node);

}

 

public abstract class NodeBase

{

// Опущен код, аналогичный предыдущему примеру

public abstract void Accept(IVisitor visitor);

}

 

public class SimpleNode: NodeBase

{

// Опущен код, аналогичный предыдущему примеру

public override void Accept(IVisitor visitor)

{

visitor.VisitSimpleNode(this);

}

}

 

public class CompositeNode: NodeBase

{

// Опущен код, аналогичный предыдущему примеру

public override void Accept(IVisitor visitor)

{

visitor.VisitCompositeNode(this);

}

}

 

public class RootNode: NodeBase

{

// Опущен код, аналогичный предыдущему примеру

public override void Accept(IVisitor visitor)

{

visitor.VisitRootNode(this);

}

}

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

public class PrintVisitor: IVisitor

{

private readonly TextWriter writer;

 

private PrintVisitor(TextWriter writer)

{

this.writer = writer;

}

 

public void VisitSimpleNode(SimpleNode node)

{

writer.WriteLine(node.Name + ": Simple");

}

 

public void VisitCompositeNode(CompositeNode node)

{

writer.WriteLine(node.Name + ": Composite");

foreach (var n in node.SimpleNodes)

{

n.Accept(this);

}

}

 

public void VisitRootNode(RootNode node)

{

writer.WriteLine(node.Name + ": Root");

foreach (var n in node.SimpleNodes)

{

n.Accept(this);

}

foreach (var n in node.CompositeNodes)

{

n.Accept(this);

}

}

 

public static void Print(NodeBase tree, TextWriter writer)

{

tree.Accept(new PrintVisitor(writer));

}

}

Если алгоритм обхода структуры всегда один и тот же, код обхода (foreach в примере) можно перенести в метод Accept(). При этом в коде реализации посетителя осуществлять обход не нужно. Однако это снижает гибкость кода, так как другой способ обхода использовать уже не удастся.

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

public interface IDoubleDispatchVisitor

{

void Visit(SimpleNode node);

void Visit(CompositeNode node);

void Visit(RootNode node);

}

 

public class SimpleNode: NodeBase

{

// Опущен код,, аналогичный предыдущему примеру

public override void Accept(IDoubleDispatchVisitor visitor)

{

visitor.Visit(this);

}

}

 

public class CompositeNode: NodeBase

{

// Опущен код,, аналогичный предыдущему примеру

public override void Accept(IDoubleDispatchVisitor visitor)

{

visitor.Visit(this);

}

}

Интерпретатор (Interpreter)

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

Сделаем небольшое отступление и рассмотрим формальные понятия язык и грамматика. Словарём будем называть выделенное конечное множество элементов. Элементы словаря будем называть символами, а последовательности символов – предложениями. Множество предложений назовём языком. Грамматикой называется любой конечный механизм задания языка. Формальная грамматика – разновидность грамматики, в которой язык задаётся при помощи порождающих правил. Каждое такое правило имеет вид «левая часть» → «правая часть», где «левая часть» – это непустая последовательность терминалов и нетерминалов, содержащая хотя бы один нетерминал, а «правая часть» – любая последовательность терминалов и нетерминалов. В свою очередь, понятие терминал означает объект, непосредственно присутствующий в словах языка, соответствующего грамматике, и имеющий конкретное, неизменяемое значение (обобщение понятия «буквы»). Нетерминал – это объект, обозначающий какую-либо сущность языка (например: формула, арифметическое выражение, команда) и не имеющий конкретного символьного значения.

Для компактного задания формальной грамматики часто используется форма Бакуса-Наура (БНФ). Вот, например, задание в БНФ грамматики, соответствующей обратной польской нотации для арифметических выражений:

expression::= plus | minus | variable

plus::= expression expression '+'

minus::= expression expression '-'

variable::= 'a' | 'b' | 'c' |... | 'z'

Вернёмся к рассмотрению шаблона Интерпретатор. Структура шаблона представлена на рис. 23. Для каждого символа (терминала и нетерминала) создаётся отдельный класс, наследуемый от общего предка. Нетерминальные классы по определению агрегируют другие объекты-символы. Каждый из классов имеет метод, ответственный за вычисление выражения, соответствующего объекту класса.

 

Рис. 23. Структура шаблона Интерпретатор.

Рассмотрим пример реализации шаблона Интерпретатор. Опишем этот пример для грамматики, которая была представлена выше в виде БНФ. Следуя шаблону, определим классы для правил грамматики.

public interface Expression

{

int Interpret(IDictionary<string, int> variables);

}

 

public class Number: Expression

{

private readonly int number;

 

public Number(int number)

{

this.number = number;

}

 

public int Interpret(IDictionary<string, int> variables)

{

return number;

}

}

 

public class Plus: Expression

{

private readonly Expression leftOperand;

private readonly Expression rightOperand;

 

public Plus(Expression left, Expression right)

{

leftOperand = left;

rightOperand = right;

}

 

public int Interpret(IDictionary<string, int> variables)

{

return leftOperand.Interpret(variables) +

rightOperand.Interpret(variables);

}

}

 

public class Minus: Expression

{

private readonly Expression leftOperand;

private readonly Expression rightOperand;

 

public Minus(Expression left, Expression right)

{

leftOperand = left;

rightOperand = right;

}

 

public int Interpret(IDictionary<string, int> variables)

{

return leftOperand.Interpret(variables) –

rightOperand.Interpret(variables);

}

}

 

public class Variable: Expression

{

private readonly string name;

 

public Variable(string name)

{

this.name = name;

}

 

public int Interpret(IDictionary<string, int> variables)

{

return variables.ContainsKey(name)? variables[name]: 0;

}

}

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

public class Evaluator

{

private readonly Expression syntaxTree;

 

public Evaluator(string expression)

{

var expressionStack = new Stack<Expression>();

foreach (var token in expression.Split(' '))

{

switch (token)

{

case "+":

var plus = new Plus(expressionStack.Pop(),

expressionStack.Pop());

expressionStack.Push(plus);

break;

case "-":

var minus = new Minus(expressionStack.Pop(),

expressionStack.Pop());

expressionStack.Push(minus);

break;

default:

expressionStack.Push(new Variable(token));

break;

}

}

syntaxTree = expressionStack.Pop();

}

 

public int Evaluate(IDictionary<string, int> context)

{

return syntaxTree.Interpret(context);

}

}

Представим код, вычисляющий выражение "w x z - +", при w=5, x=10, z=4.

public class InterpreterExample

{

private static void Main()

{

var sentence = new Evaluator("w x z - +");

var vars = new Dictionary<string, int> { { "w", 5 },

{ "x", 10 }, { "z", 4 } };

Console.WriteLine(sentence.Evaluate(vars));

}

}

Хранитель (Memento)

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

 

Рис. 23. UML-диаграмма шаблона Хранитель.

На рис. 23 показана UML-диаграмма шаблона хранитель. Ниже описаны элементы диаграммы.

Составитель. Этот тот класс, состояние которого предполагается сохранять. Составитель включает метод CreateMemento(), который используется для генерации объекта-хранителя, содержащего состояние составителя. Также составитель содержит метод SetMemento(), восстанавливающий состояние по хранителю.

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

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

Рассмотрим пример реализации шаблона Хранитель. В примере описывается набор классов для поддержки функции Undo в библиотечной системе. Класс-составитель представляет информацию о книге, редактируемую пользователем. Если пользователь делает ошибку, он может выполнить операцию Undo для возврата книги в первоначальное состояние.

public class Book

{

private string _isbn;

private string _title;

private string _author;

private DateTime _lastEdited;

 

public string ISBN

{

get { return _isbn; }

set

{

_isbn = value;

SetLastEdited();

}

}

 

public string Title

{

get { return _title; }

set

{

_title = value;

SetLastEdited();

}

}

 

public string Author

{

get { return _author; }

set

{

_author = value;

SetLastEdited();

}

}

 

public Book()

{

SetLastEdited();

}

 

private void SetLastEdited()

{

_lastEdited = DateTime.UtcNow;

}

 

public Memento CreateUndo()

{

return new Memento(_isbn, _title, _author, _lastEdited);

}

 

public void RestoreFromUndo(Memento memento)

{

_title = memento.Title;

_author = memento.Author;

_isbn = memento.ISBN;

_lastEdited = memento.LastEdited;

}

 

public void ShowBook()

{

Console.WriteLine("{0} - '{1}' by {2}, edited {3}.",

ISBN, Title, Author, _lastEdited);

}

}

 

public class Memento

{

public string ISBN { get; private set; }

public string Title { get; private set; }

public string Author { get; private set; }

public DateTime LastEdited { get; private set; }

 

public Memento(string isbn, string title, string author,

DateTime lastEdited)

{

ISBN = isbn;

Title = title;

Author = author;

LastEdited = lastEdited;

}

}

3.12. НЕКОТОРЫЕ НЕКЛАССИЧЕСКИЕ ШАБЛОНЫ ПРОЕКТИРОВАНИЯ

Набор шаблонов проектирования не ограничивается 23 шаблонами из книги «Design Patterns». Со временем список шаблонов расширялся. Некоторые популярные шаблоны представлены в данном параграфе.



Поделиться:


Последнее изменение этой страницы: 2016-09-13; просмотров: 57; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 13.58.121.131 (0.527 с.)