Здесь я хотел бы рассказать о паттерне стратегии.
В качестве примера, рассмотрим следующий класс:
Ниже я показал диаграмму классов (у нас две новых утки: MallarDuck и RedHeadDuck, которые наследуют абстрактный класс Duck):
Класс MallarDuck:
В качестве примера, рассмотрим следующий класс:
abstract class Duck { public void quack() { Console.Out.WriteLine("Quack!"); } public void swim() { Console.Out.WriteLine("Bulk!"); } abstract public void display(); }Это наш класс Утка, которая умеет крякать и плавать, при этом все утки, которые наследуют этот класс умеют тоже самое. Метод display у нас является абстрактным, так как каждая утка имеет свой образ и требует конкретной реализации.
Ниже я показал диаграмму классов (у нас две новых утки: MallarDuck и RedHeadDuck, которые наследуют абстрактный класс Duck):
Класс MallarDuck:
using System; class MallarDuck : Duck { public override void display() { Console.Out.WriteLine("I am Mallar Duck!"); } }
Класс RedHeadDuck:
using System; class RedHeadDuck : Duck { public override void display() { Console.Out.WriteLine("I am Red Head Duck!"); } }Запускаем наш первый пример:
using System; class Program { static void Main(string[] args) { MallarDuck MyDuckMallar = new MallarDuck(); RedHeadDuck MyDuckRedHead = new RedHeadDuck(); MyDuckMallar.display(); MyDuckRedHead.display(); MyDuckMallar.quack(); MyDuckRedHead.quack(); Console.In.ReadLine(); } } Результатом запуска будет:
Если мы заходим дать возможность нашим уткам летать, то необходимо будет добавить в наш абстрактный класс Duck метод fly(), при этом его конкретную реализацию унаследуют все классы, т.е. даже те утки, которые не должны летать. Для них нам придется переписывать данный метод и делать его пустым (например деревянная утка не должна уметь летать). При добавлении других возможностей в будущем, каждый раз придется реализовывать пустые методы.
Какой Выход?
Возможно, стоит вынести метод fly() в интерфейс? Тогда мы достигним того, что деревянная утка не должна летать, так как она не будет реализовывать интерфейс метода fly().
Но тут возникает другая проблема: дублирование кода. Так как интерфейс не может содержать конкретную реализацию метода, то нам придется реализовывать метод fly() для каждого класса. При каких-либо изменениях, необходимо будет внести их в каждый класс.
Основным принципом проектирования является: Выделить части приложения, которые могут изменяться и отделить их от тех, которые являются постоянными. Иными словами нам нужно выделить изменяющийся код так, чтобы не изменять постоянную часть в дальнейшем.
Руководствуясь этими двумя принципами переделаем наш пример.
Мы знаем, что метод fly() может быть не у всех субклассов класса Duck, поэтому нам необходимо выделить его в отдельный набор классов, так же как и метод кряканья quack().
Руководствуясь первый принципом мы должны создать интерфейс для методы fly()
Получаем следующую диаграмму классов:
Давайте посмотрим на изменившийся класс Duck:
abstract class Duck { //переменные интерфейсов, //через которые будет происходит делегирование действий protected IFlyBehavior flyBehavior; protected IQuackBehavior quackBehavior; //создаем динамическое изменение поведения уток public void setFlyBehavior(IFlyBehavior fb) { flyBehavior = fb; } public void setQuackBehavior(IQuackBehavior qb) { quackBehavior = qb; } public void performQuack() { quackBehavior.quak(); //делегируем } public void performFly() { flyBehavior.fly(); //делегируем } public void swim() { Console.Out.WriteLine("Bulk!"); } abstract public void display(); }Мы вынесли методы полета и кряканья в интерфейсы, а также сделали набор классов, которые являются реализацией разных алгоритмов полета и кряканья наших уток. Далее мы объявили две переменных в абстрактном классе Duck()
protected IFlyBehavior flyBehavior; protected IQuackBehavior quackBehavior;Которые ссылаются на классы, реализующие данные методы. Т.е. мы можем присвоить любой алгоритм кряканья или полета. Вызов делегирование происходит через методы:
public void performQuack() { quackBehavior.quak(); //делегируем } public void performFly() { flyBehavior.fly(); //делегируем }С помощью двух сеттеров мы можем динамически на лету присваивать разные алгоритмы нашим классам. Метод display() мы оставили абстрактным, так как для каждой утки он будет разным, а метод swim() для всех будет одинаков. Ниже приведу описание остальных классов:
//интерфейс метода Полета interface IFlyBehavior { void fly(); } // интерфейс метода кряканья interface IQuackBehavior { void quak(); } //класс, реализующий невозможность летать class FlyNoWay: IFlyBehavior { public void fly() { Console.Out.WriteLine("I can not fly!"); } } //Класс, реализующий алгоритм полета ракеты class FlyRocket: IFlyBehavior { public void fly() { Console.Out.WriteLine("I fly as rocket!"); } } //Класс, реализующий алгоритм обычного кряканья class Quack : IQuackBehavior { public void quak() { Console.Out.WriteLine("quack!"); } } //Класс, реализующий алгоритм бесшумного кряканья class MuteQuack: IQuackBehavior { public void quak() { Console.Out.WriteLine("Empty Quack!"); } } } //Класс дикой утки public MallarDuck() { quackBehavior = new Quack(); flyBehavior = new FlyRocket(); } public override void display() { Console.Out.WriteLine("I am Mallar Duck!"); } } //Класс красноголовой утки class RedHeadDuck : Duck { public RedHeadDuck() { quackBehavior = new Quack(); flyBehavior = new FlyRocket(); } public override void display() { Console.Out.WriteLine("I am Red Head Duck!"); } }Давайте теперь сделаем класс деревянной утки, для этого сделаем класс-модель утки, которая по умолчанию не умеет ничего делать:
class ModelDuck : Duck { public ModelDuck() { quackBehavior = new MuteQuack(); flyBehavior = new FlyNoWay(); } public override void display() { Console.Out.WriteLine("I am model!"); } }Теперь давайте вызовем все наши классы, а также посмотрим как можно динамически изменять алгоритмы:
class Program { static void Main(string[] args) { MallarDuck MyDuckMallar = new MallarDuck(); RedHeadDuck MyDuckRedHead = new RedHeadDuck(); //Наша дикая утка MyDuckMallar.display(); MyDuckMallar.performFly(); MyDuckMallar.performQuack(); //красноголовая утка MyDuckRedHead.display(); MyDuckRedHead.performFly(); MyDuckRedHead.performQuack(); //модель утки, которой мы поменяли алгоритм //кряканья и алгоритм полета ModelDuck myThreeDuck = new ModelDuck(); myThreeDuck.display(); myThreeDuck.performFly(); myThreeDuck.performQuack(); myThreeDuck.setFlyBehavior(new FlyRocket()); myThreeDuck.setQuackBehavior(new Quack()); myThreeDuck.performFly(); myThreeDuck.performQuack(); Console.In.ReadLine(); } }
Вот это да! Мы смогли избавиться от дублировния кода, теперь мы можем независимо разрабатывать алгоритмы и присваивать их любым уткам, а также мы смогли уйти от наследования, которое принудительно добавляло все методы во все субклассы!
Подобные связи между двумя классами называются композицией, т.е. поведение не наследуется, а предоставляется правилами выбора.
Принцип проектирования: Отдавайте предпочтение композиции перед наследованием.
Композиция позволяет не только инкапсулировать семейство алгоритмов, но и изменять поведение во время выполнения.
Паттерн СТРАТЕГИЯ определяет семейство алгоритмов, инкапсулирует каждый из них и обеспечивает их взаимозаменяемость. Он позволяет модифицировать алгоритмы независимо от их использования на стороне клиента.
Комментариев нет:
Отправить комментарий