Главная страница
Навигация по странице:

  • Практическая работа №16

  • Когда надо применять паттерн

  • Участники Абстрактный класс Product

  • Creator

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

  • Когда использовать абстрактную фабрику

  • AbstractProductA и AbstractProductB

  • ConcreteFactory1 и ConcreteFactory2

  • Методические рекомендации по выполнению практических работ по междисциплинарному курсу


    Скачать 2.6 Mb.
    НазваниеМетодические рекомендации по выполнению практических работ по междисциплинарному курсу
    Дата05.09.2022
    Размер2.6 Mb.
    Формат файлаpdf
    Имя файлаMU_PR_MDK_01_01.pdf
    ТипМетодические рекомендации
    #663423
    страница7 из 13
    1   2   3   4   5   6   7   8   9   10   ...   13
    Интерфейсы или абстрактные классы

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

    Если надо определить общий функционал для родственных объектов

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

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

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

    Если мы проектируем небольшой функциональный тип
    Ключевыми здесь являются первые пункты, которые можно свести к следующему принципу: если классы относятся к единой системе классификации, то выбирается абстрактный класс. Иначе выбирается интерфейс. Посмотрим на примере.
    Допустим, у нас есть система транспортных средств: легковой автомобиль, автобус, трамвай, поезд и т.д. Поскольку данные объекты являются родственными, мы можем выделить у них общие признаки, то в данном случае можно использовать абстрактные классы:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 14 15 16 17 18 19 20 21 public abstract class Vehicle
    { public abstract void Move();
    } public class Car : Vehicle
    { public override void Move()
    {
    Console.WriteLine("Машина едет");
    }
    } public class Bus : Vehicle
    { public override void Move()
    {
    Console.WriteLine("Автобус едет");
    }
    }

    22 23 24 25 26 27 28 public class Tram : Vehicle
    { public override void Move()
    {
    Console.WriteLine("Трамвай едет");
    }
    }
    Абстрактный класс Vehicle определяет абстрактный метод перемещения Move(), а классы- наследники его реализуют.
    Но, предположим, что наша система транспорта не ограничивается вышеперечисленными транспортными средствами. Например, мы можем добавить самолеты, лодки. Возможно, также мы добавим лошадь - животное, которое может также выполнять роль транспортного средства. Также можно добавить дирижабль. Вобщем получается довольно широкий круг объектов, которые связаны только тем, что являются транспортным средством и должны реализовать некоторый метод Move(), выполняющий перемещение.
    Так как объекты малосвязанные между собой, то для определения общего для всех них функционала лучше определить интерфейс. Тем более некоторые из этих объектов могут существовать в рамках параллельных систем классификаций. Например, лошадь может быть классом в структуре системы классов животного мира.
    Возможная реализация интерфейса могла бы выглядеть следующим образом:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public interface IMovable
    { void Move();
    } public abstract class Vehicle
    {} public class Car : Vehicle, IMovable
    { public void Move()
    {
    Console.WriteLine("Машина едет");
    }
    } public class Bus : Vehicle, IMovable
    { public void Move()
    {
    Console.WriteLine("Автобус едет");
    }
    } public class Hourse : IMovable
    { public void Move()
    {
    Console.WriteLine("Лошадь скачет");
    }
    }

    33 34 35 36 37 38 39 public class Aircraft : IMovable
    { public void Move()
    {
    Console.WriteLine("Самолет летит");
    }
    }
    Теперь метод Move() определяется в интерфейсе IMovable, а конкретные классы его реализуют.
    Говоря об использовании абстрактных классов и интерфейсов можно привести еще такую аналогию, как состояние и действие. Как правило, абстрактные классы фокусируются на общем состоянии классов-наследников. В то время как интерфейсы строятся вокруг какого-либо общего действия.
    Например, солнце, костер, батарея отопления и электрический нагреватель выполняют функцию нагревания или излучения тепла. По большому счету выделение тепла - это единственный общий между ними признак. Можно ли для них создать общий абстрактный класс? Можно, но это не будет оптимальным решением, тем более у нас могут быть какие-то родственные сущности, которые мы, возможно, тоже захотим использовать. Поэтому для каждой вышеперечисленной сущности мы можем определить свою систему классификации. Например, в одной системе классов, которые наследуются от общего астрактного класса, были бы звезды, в том числе и солнце, планеты, астероиды и так далее - то есть все те объекты, которые могут иметь какое-то общее с солнцем состояние. В рамках другой системы классов мы могли бы определить электрические приборы, в том числе электронагреатель. И так, для каждой разноплановой сущности можно было бы составить свою систему классов, исходяющую от определенного абстрактного класса. А для общего действия определить интерфейс, например, IHeatable, в котором бы был метод Heat, и этот интерфейс реализовать во всех необходимых классах.
    Таким образом, если разноплановые классы обладают каким-то общим действием, то это действие лучше выносить в интерфейс. А для одноплановых классов, которые имеют общее состояние, лучше определять абстрактный класс.

    Практическая работа №16 Использование порождающих шаблонов
    Фабричный метод (Factory Method)
    Фабричный метод (Factory Method) - это паттерн, который определяет интерфейс для создания объектов некоторого класса, но непосредственное решение о том, объект какого класса создавать происходит в подклассах. То есть паттерн предполагает, что базовый класс делегирует создание объектов классам-наследникам.
    Когда надо применять паттерн

    Когда заранее неизвестно, объекты каких типов необходимо создавать

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

    Когда создание новых объектов необходимо делегировать из базового класса классам наследникам
    На языке UML паттерн можно описать следующим образом:
    Формальное определение паттерна на языке C# может выглядеть следующим образом:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 abstract class Product
    {} class ConcreteProductA : Product
    {} class ConcreteProductB : Product
    {} abstract class Creator
    { public abstract Product FactoryMethod();
    } class ConcreteCreatorA : Creator
    { public override Product FactoryMethod() { return new ConcreteProductA(); }
    } class ConcreteCreatorB : Creator
    { public override Product FactoryMethod() { return new ConcreteProductB(); }
    }
    Участники

    Абстрактный класс Product определяет интерфейс класса, объекты которого надо создавать.

    Конкретные классы ConcreteProductA и ConcreteProductB представляют реализацию класса Product. Таких классов может быть множество

    Абстрактный класс Creator определяет абстрактный фабричный метод FactoryMethod(), который возвращает объект Product.

    Конкретные классы ConcreteCreatorA и ConcreteCreatorB - наследники класса
    Creator, определяющие свою реализацию метода FactoryMethod().
    Причем метод FactoryMethod() каждого отдельного класса-создателя возвращает определенный конкретный тип продукта. Для каждого конкретного класса продукта определяется свой конкретный класс создателя.

    Таким образом, класс Creator делегирует создание объекта Product своим наследникам. А классы ConcreteCreatorA и ConcreteCreatorB могут самостоятельно выбирать какой конкретный тип продукта им создавать.
    Теперь рассмотрим на реальном примере. Допустим, мы создаем программу для сферы строительства. Возможно, вначале мы захотим построить многоэтажный панельный дом.
    И для этого выбирается соответствующий подрядчик, который возводит каменные дома.
    Затем нам захочется построить деревянный дом и для этого также надо будет выбрать нужного подрядчика:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Program
    { static void Main(string[] args)
    {
    Developer dev = new PanelDeveloper("ООО КирпичСтрой");
    House house2 = dev.Create(); dev = new WoodDeveloper("Частный застройщик");
    House house = dev.Create();
    Console.ReadLine();
    }
    }
    // абстрактный класс строительной компании abstract class Developer
    { public string Name { get; set; } public Developer (string n)
    {
    Name = n;
    }
    // фабричный метод abstract public House Create();
    }
    // строит панельные дома class PanelDeveloper : Developer
    { public PanelDeveloper(string n) : base(n)
    { } public override House Create()
    { return new PanelHouse();
    }
    }
    // строит деревянные дома class WoodDeveloper : Developer
    { public WoodDeveloper(string n) : base(n)
    { } public override House Create()
    {

    45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 return new WoodHouse();
    }
    } abstract class House
    { } class PanelHouse : House
    { public PanelHouse()
    {
    Console.WriteLine("Панельный дом построен");
    }
    } class WoodHouse : House
    { public WoodHouse()
    {
    Console.WriteLine("Деревянный дом построен");
    }
    }
    В качестве абстрактного класса Product здесь выступает класс House. Его две конкретные реализации - PanelHouse и WoodHouse представляют типы домов, которые будут строить подрядчики. В качестве абстрактного класса создателя выступает Developer, определяющий абстрактный метод Create(). Этот метод реализуется в классах- наследниках WoodDeveloper и PanelDeveloper. И если в будущем нам потребуется построить дома какого-то другого типа, например, кирпичные, то мы можем с легкостью создать новый класс кирпичных домов, унаследованный от House, и определить класс соответствующего подрядчика. Таким образом, система получится легко расширяемой.
    Правда, недостатки паттерна тоже очевидны - для каждого нового продукта необходимо создавать свой класс создателя.
    Абстрактная фабрика (Abstract Factory)
    Паттерн "Абстрактная фабрика" (Abstract Factory) предоставляет интерфейс для создания семейств взаимосвязанных объектов с определенными интерфейсами без указания конкретных типов данных объектов.
    Когда использовать абстрактную фабрику

    Когда система не должна зависеть от способа создания и компоновки новых объектов

    Когда создаваемые объекты должны использоваться вместе и являются взаимосвязанными
    С помощью UML абстрактную фабрику можно представить следующим образом:
    Формальное определение паттерна на языке C# может выглядеть следующим образом:
    1 2
    3 4
    5 6
    7 8
    9 abstract class AbstractFactory
    { public abstract AbstractProductA CreateProductA(); public abstract AbstractProductB CreateProductB();
    } class ConcreteFactory1: AbstractFactory
    { public override AbstractProductA CreateProductA()
    {

    10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 return new ProductA1();
    } public override AbstractProductB CreateProductB()
    { return new ProductB1();
    }
    } class ConcreteFactory2: AbstractFactory
    { public override AbstractProductA CreateProductA()
    { return new ProductA2();
    } public override AbstractProductB CreateProductB()
    { return new ProductB2();
    }
    } abstract class AbstractProductA
    {} abstract class AbstractProductB
    {} class ProductA1: AbstractProductA
    {} class ProductB1: AbstractProductB
    {} class ProductA2: AbstractProductA
    {} class ProductB2: AbstractProductB
    {} class Client
    { private AbstractProductA abstractProductA; private AbstractProductB abstractProductB; public Client(AbstractFactory factory)
    { abstractProductB = factory.CreateProductB(); abstractProductA = factory.CreateProductA();
    } public void Run()
    { }
    }

    Паттерн определяет следующих участников:

    Абстрактные классы AbstractProductA и AbstractProductB определяют интерфейс для классов, объекты которых будут создаваться в программе.

    Конкретные классы ProductA1
    /
    ProductA2 и ProductB1
    /
    ProductB2 представляют конкретную реализацию абстрактных классов

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

    Конкретные классы фабрик ConcreteFactory1 и ConcreteFactory2 реализуют абстрактные методы базового класса и непосредственно определяют какие конкретные продукты использовать

    Класс клиента Client использует класс фабрики для создания объектов. При этом он использует исключительно абстрактный класс фабрики AbstractFactory и абстрактные классы продуктов AbstractProductA и AbstractProductB и никак не зависит от их конкретных реализаций
    Посмотрим, как мы можем применить паттерн. Например, мы делаем игру, где пользователь должен управлять некими супергероями, при этом каждый супергерой имеет определенное оружие и определенную модель передвижения. Различные супергерои могут определяться комплексом признаков. Например, эльф может летать и должен стрелять из арбалета, другой супергерой должен бегать и управлять мечом. Таким образом, получается, что сущность оружия и модель передвижения являются взаимосвязанными и используются в комплексе. То есть имеется один из доводов в пользу использования абстрактной фабрики.
    И кроме того, наша задача при проектировании игры абстрагировать создание супергероев от самого класса супергероя, чтобы создать более гибкую архитектуру. И для этого применим абстрактную фабрику:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Program
    { static void Main(string[] args)
    {
    Hero elf = new Hero(new ElfFactory()); elf.Hit(); elf.Run();
    Hero voin = new Hero(new VoinFactory()); voin.Hit(); voin.Run();
    Console.ReadLine();
    }
    }
    //абстрактный класс - оружие abstract class Weapon
    { public abstract void Hit();
    }
    // абстрактный класс движение abstract class Movement
    { public abstract void Move();
    }

    27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
    // класс арбалет class Arbalet : Weapon
    { public override void Hit()
    {
    Console.WriteLine("Стреляем из арбалета");
    }
    }
    // класс меч class Sword : Weapon
    { public override void Hit()
    {
    Console.WriteLine("Бьем мечом");
    }
    }
    // движение полета class FlyMovement : Movement
    { public override void Move()
    {
    Console.WriteLine("Летим");
    }
    }
    // движение - бег class RunMovement : Movement
    { public override void Move()
    {
    Console.WriteLine("Бежим");
    }
    }
    // класс абстрактной фабрики abstract class HeroFactory
    { public abstract Movement CreateMovement(); public abstract Weapon CreateWeapon();
    }
    // Фабрика создания летящего героя с арбалетом class ElfFactory : HeroFactory
    { public override Movement CreateMovement()
    { return new FlyMovement();
    } public override Weapon CreateWeapon()
    { return new Arbalet();
    }
    }
    // Фабрика создания бегущего героя с мечом

    79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 class VoinFactory : HeroFactory
    { public override Movement CreateMovement()
    { return new RunMovement();
    } public override Weapon CreateWeapon()
    { return new Sword();
    }
    }
    // клиент - сам супергерой class Hero
    { private Weapon weapon; private Movement movement; public Hero(HeroFactory factory)
    { weapon = factory.CreateWeapon(); movement = factory.CreateMovement();
    } public void Run()
    { movement.Move();
    } public void Hit()
    { weapon.Hit();
    }
    }
    Таким образом, создание супергероя абстрагируется от самого класса супергероя. В то же время нельзя не отметить и недостатки шаблона. В частности, если нам захочется добавить в конфигурацию супергероя новый объект, например, тип одежды, то придется переделывать классы фабрик и класс супергероя. Поэтому возможности по расширению в данном паттерне имеют некоторые ограничения.
    Одиночка
    Одиночка (Singleton, Синглтон) - порождающий паттерн, который гарантирует, что для определенного класса будет создан только один объект, а также предоставит к этому объекту точку доступа.
    Когда надо использовать Синглтон? Когда необходимо, чтобы для класса существовал только один экземпляр
    Синглтон позволяет создать объект только при его необходимости. Если объект не нужен, то он не будет создан. В этом отличие синглтона от глобальных переменных.
    Классическая реализация данного шаблона проектирования на C# выглядит следующим образом:
    1 2
    3 4
    5 6 class Singleton
    { private static Singleton instance; private Singleton()
    {}

    7 8
    9 10 11 12 13 14 public static Singleton getInstance()
    { if (instance == null) instance = new Singleton(); return instance;
    }
    }
    В классе определяется статическая переменная - ссылка на конкретный экземпляр данного объекта и приватный конструктор. В статическом методе getInstance() этот конструктор вызывается для создания объекта, если, конечно, объект отсутствует и равен null.
    Для применения паттерна Одиночка создадим небольшую программу. Например, на каждом компьютере можно одномоментно запустить только одну операционную систему.
    В этом плане операционная система будет реализоваться через паттерн синглтон:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Program
    { static void Main(string[] args)
    {
    Computer comp = new Computer(); comp.Launch("Windows 8.1");
    Console.WriteLine(comp.OS.Name);
    // у нас не получится изменить ОС, так как объект уже создан comp.OS = OS.getInstance("Windows 10");
    Console.WriteLine(comp.OS.Name);
    Console.ReadLine();
    }
    } class Computer
    { public OS OS { get; set; } public void Launch(string osName)
    {
    OS = OS.getInstance(osName);
    }
    } class OS
    { private static OS instance; public string Name { get; private set; } protected OS(string name)
    { this.Name=name;
    } public static OS getInstance(string name)
    { if (instance == null) instance = new OS(name);

    39 40 41 return instance;
    }
    }
    1   2   3   4   5   6   7   8   9   10   ...   13


    написать администратору сайта