Wzorzec projektowy Twoim wzorcem cz.2. Prototyp / Design Pattern is your pattern pt.2. Prototype

PL

Omówienie

W tym wpisie, kolejnym z serii dotyczącej kreacyjnych wzorców projektowych, omówię wzorzec projektowy Prototype – Prototyp.

Prototyp, zgodnie z intuicją, ma za zadanie dostarczyć jakiś pierwotny obiekt, który potem wykorzystamy do konkretnych celów. Tym celem będzie tworzenie obiektów interesujących klienta.

W poprzednim wpisie pisałem o wzorcu Metoda Fabryczna, która powołuje konkretne instancje klas domeny i zdejmouje tą odpowiedzialność z klienta aplikacji. Jeśli chodzi o prototyp, to działa on podobnie, z tą różnicą, że w jego implementacji instancje konkretnych klas domeny są już powołane, a jego zadaniem jest ich klonowanie i dostarczenie klientowi.

Prototyp, jako swego rodzaju factory – fabryka, dostarcza logikę wdrażającą w lifetime aplikacji zestaw instancji klas domen, wszystkich możliwych do powołania, których co ważne, może być wiele, podobnie jak w przypadku Fabryki Abstrakcyjnej, oraz logikę odpowiedzialną za ich klonowanie i dostarczanie klientowi aplikacji.

Prototype może bazować na implementacji z wykorzystaniem klasy abstrakcyjnej jak i interfesju, w mojej implementacji, która jak zawsze znajduje się na końcu posta, wykorzystałem interfejsy.

Czemu?

  • Zapewnia enkapsulację powoływania nowej instancji klasy oraz nie wymaga użycia słowa kluczowego new;
  • Brak konieczności powoływania nowej instancji klasy lub kilku klas przez klienta bezpośrednio, tylko przy użyciu prototypu
  • Możliwość implementacji z wykorzystaniem dowolnej liczby klas dzięki dostarczonemu przez prototyp zestawowi instancji klas domen możliwych do wykorzystania w logice aplikacji
  • Prototyp ,,spina” ze sobą wszystkie możliwe do wykorzystania instancje klas domeny co ułatwia korzystanie z aplikacji oraz jej utrzymywanie
  • Konstrukcja interfejsu dostarczającego implementację dla klas domeny wspiera praktykę loose coupling, czyli słabego/luźnego powiązania w architekturze aplikacji.

Dla tych z Was, którzy nie spotkali się z tym pojęciem, luźne powiązanie pozwala na zachowanie niezależności między modułami aplikacji, braku zależności między ich implementacjami, podczas jednoczesnego zachowania komunikacji między nimi. Jest to bardzo ważny paradygmat projektowania architektury aplikacji.

Jak?

  • Dostarcza interfejs będący fabryką dla zestawu instancji klas domeny możliwych do powołania w aplikacji
  • Prototyp fabryka zawiera w implementacji zestaw wszystkich możliwych do wykorzystania przez klienta aplikacji instancji klas domeny
  • Prototyp zawiera w implementacji metodę dostarczającą klientowi aplikacji odpowiednią sklonowaną instancję klasy domeny na podstawie wskazanej wartości – zaleca się używanie do tego celu konstrukcji enum, takie użycie też znajduje się w przykładowej implementacji wzorca pod postem
  • Metoda klonująca wskazaną instancję klasy domeny zaimplementowana jest w tej klasie domeny

Przykładowa implementacja w C#


   // Prototype interface implemented by Prototype Factory class
    public interface IPrototypeFactory
    {
        Dictionary<ProductsEnum, IPrototype> Prototypes { get; set; }
        IPrototype GetPrototype(ProductsEnum prototypeName);
        void SetPrototypes();
    }

    /// <summary>
    /// Prototype factory class with logic responsible for delivering concrete implementation of product 
    /// particular product classes
    /// </summary>
    public class PrototypeFactory : IPrototypeFactory
    {
        public PrototypeFactory()
        {
        }

        // dictionary containing instances of Product classes and its actual names
        public Dictionary<ProductsEnum, IPrototype> Prototypes { get; set; }

        /// <summary>
        /// Delivers concrete instance of the IPrototype Product class
        /// </summary>
        /// <param name="prototypeName"></param>
        /// <returns>Concrete IPrototype Product class instance</returns>
        public IPrototype GetPrototype(ProductsEnum prototypeName)
        {
            IPrototype prototype;
            
            if (Prototypes.ContainsKey(prototypeName))
                prototype = Prototypes[prototypeName].Clone();
            else
                throw new KeyNotFoundException();

            return prototype;
        }

        /// <summary>
        /// Initializes dictionary containing product instances possible to use in the logic of application.
        /// Dictinary delivers object on based which decision concerning invoking concrete product instances will be made
        /// </summary>
        public void SetPrototypes()
        {
            this.Prototypes = new Dictionary<ProductsEnum, IPrototype>();

            Prototypes.Add(ProductsEnum.ProductA, new ProductA("productA"));
            Prototypes.Add(ProductsEnum.ProductB, new ProductB("productB"));
        }
    }

    // enum that delivers possible Product classes values
    public enum ProductsEnum
    {
        ProductA = 1,
        ProductB = 2,
        ProductC = 3
    }

    /// <summary>
    /// Prototype interface being implemented by Product classes
    /// </summary>
    public interface IPrototype
    {
        IPrototype Clone();
    }

    /// <summary>
    /// represents class which instance client can create with prototype
    /// </summary>
    public class ProductA : IPrototype
    {
        public ProductA()
        {

        }
        public ProductA(string name)
        {
            Name = name;
        }

        /// <summary>
        /// enables getting proper class type while invoking Prototype Factory's method
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// clones instance of the actual class
        /// </summary>
        /// <returns>instance of the actual class</returns>
        public IPrototype Clone()
        {
            return (ProductA) this.MemberwiseClone();
        }
    }

Przykładowe powołanie instacji klasy domeny ProductA z wykorzystaniem prototypu PrototypeFactory oraz kontenera Dependency Injection Autofac:


DesignPatternsDiConfig.RegisterTypes();

var factory = DesignPatternsDiConfig.prototypeFactory;
factory.SetPrototypes();

factory.GetPrototype(CreationalPatterns.Prototype.Classes.ProductsEnum.ProductA);

var product = factory.GetPrototype(CreationalPatterns.Prototype.Classes.ProductsEnum.ProductA);

Console.WriteLine(product); 

,gdzie klasa DesignPatternsDiConfig ma następującą implementację (implementacja rozszerzona względem poprzedniego postu):


    /// <summary>
    /// responsible for registrating and resolving types using IoC container
    /// </summary>
    public class DesignPatternsDiConfig
    {
        private static IContainer _container;
        
        // public IoC variables being used through application after being resolved
        public static DesignPatterns.CreationalPatterns.Prototype.Interfaces.IPrototypeFactory prototypeFactory;
        public static DesignPatterns.CreationalPatterns.FactoryMethod.Interfaces.IFactory factoryMethodfactory;


        /// <summary>
        /// registers and resolve delivered types according to the IoC container
        /// </summary>
        public static void RegisterTypes()
        {
            var builder = new ContainerBuilder();

            // Prototype factories registering
            builder.RegisterType<PrototypeFactory>().AsSelf();
            builder.RegisterType<PrototypeFactory>().As<DesignPatterns.CreationalPatterns.Prototype.Interfaces.IPrototypeFactory>();

            // Factory Method factories registering
            builder.RegisterType<DesignPatterns.CreationalPatterns.FactoryMethod.Factories.Factory>().AsSelf();
            builder.RegisterType<DesignPatterns.CreationalPatterns.FactoryMethod.Factories.Factory>()
                .As<DesignPatterns.CreationalPatterns.FactoryMethod.Interfaces.IFactory>();


            //
            // builds container -> important: can be performed only once within current application context's live
            //
            _container = builder.Build();

            //resolving Prototype factory
            prototypeFactory = _container.Resolve<PrototypeFactory>();

            //resolving Factory Method factory
            factoryMethodfactory = _container.Resolve<CreationalPatterns.FactoryMethod.Factories.Factory>();

        }
    }

Wróć na stronę startową

ENG

Introduction

In this post, another from series concerning creational design pattern I am going to talk about Prototype design pattern.

Intuitively, Prototype is responsible for delivering some primary object which after would be used to concrete purposes, in this case these purposes will be creating objects that client of application is interested in.

In the previous posts I wrote about creational design pattern concerning factories which are responsible for invoking domain classes’ instances and relieved this responsibility away from client of the application. When we talk about Prototype, the mechanism is very similar, with the difference that in itsimplementation instances of concrete classes of a domain are already invoked and its job is to clone and deliver them to a client.

Prototype, as factory, delivers logic that implementing in application lifetime set of instances of domain classes, every being possible to invoke, which can be many, which is important from the point of view comparing to Abstract Factory design pattern, about which post will be posted soon.

Prototype can base on a implementation with use of both an abstract class and interface, in my implementation placed like always below the content of this post, I used interface implementation.

Why?

  • Provides encapsulation of invoking new class’s instance and does not require using new keyword
  • It is not necessary to invoke instance of a new class or instances of more than one class directly, but using exactly prototype as the factory
  • Possibility of implementation using any number of domain classes according to delivered by the prototype set of instances of domain classes, possible to use by the client of the application
  • Prototype merges all possible to use in the application instances of domain classes which make it easier to use and maintain the application
  • Construction of the mentioned interface supports loose coupling architecture paradigm

For those who have never met with this notion, loose coupling allows to keep independence between application modules, between their implementations, while keeping communication between them. It is very important paradigm of an application architecture project.

How?

  • Delivers interface which is a factory for set of domain class instances possible to be invoked within application
  • Prototype contains set of abovementioned instances within its implementation
  • Prototype contains within its implementation method delivering to the client of the application proper cloned instance of a domain class and this choice is made on base delivered value – it is desirable to use enum construction, and that is what I used in implementation below
  • Method cloning concrete instance of a domain class is implemented exactly within that domain class

Example of implementation in C#


// Prototype interface implemented by Prototype Factory class
    public interface IPrototypeFactory
    {
        Dictionary<ProductsEnum, IPrototype> Prototypes { get; set; }
        IPrototype GetPrototype(ProductsEnum prototypeName);
        void SetPrototypes();
    }

    /// <summary>
    /// Prototype factory class with logic responsible for delivering concrete implementation of product 
    /// particular product classes
    /// </summary>
    public class PrototypeFactory : IPrototypeFactory
    {
        public PrototypeFactory()
        {
        }

        // dictionary containing instances of Product classes and its actual names
        public Dictionary<ProductsEnum, IPrototype> Prototypes { get; set; }

        /// <summary>
        /// Delivers concrete instance of the IPrototype Product class
        /// </summary>
        /// <param name="prototypeName"></param>
        /// <returns>Concrete IPrototype Product class instance</returns>
        public IPrototype GetPrototype(ProductsEnum prototypeName)
        {
            IPrototype prototype;
            
            if (Prototypes.ContainsKey(prototypeName))
                prototype = Prototypes[prototypeName].Clone();
            else
                throw new KeyNotFoundException();

            return prototype;
        }

        /// <summary>
        /// Initializes dictionary containing product instances possible to use in the logic of application.
        /// Dictinary delivers object on based which decision concerning invoking concrete product instances will be made
        /// </summary>
        public void SetPrototypes()
        {
            this.Prototypes = new Dictionary<ProductsEnum, IPrototype>();

            Prototypes.Add(ProductsEnum.ProductA, new ProductA("productA"));
            Prototypes.Add(ProductsEnum.ProductB, new ProductB("productB"));
        }
    }

    // enum that delivers possible Product classes values
    public enum ProductsEnum
    {
        ProductA = 1,
        ProductB = 2,
        ProductC = 3
    }

    /// <summary>
    /// Prototype interface being implemented by Product classes
    /// </summary>
    public interface IPrototype
    {
        IPrototype Clone();
    }

    /// <summary>
    /// represents class which instance client can create with prototype
    /// </summary>
    public class ProductA : IPrototype
    {
        public ProductA()
        {

        }
        public ProductA(string name)
        {
            Name = name;
        }

        /// <summary>
        /// enables getting proper class type while invoking Prototype Factory's method
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// clones instance of the actual class
        /// </summary>
        /// <returns>instance of the actual class</returns>
        public IPrototype Clone()
        {
            return (ProductA) this.MemberwiseClone();
        }
    }

Example of invoking instance of the domain class ProductA with use of prototype PrototypeFactory and Autofac Dependency Injection container:


DesignPatternsDiConfig.RegisterTypes();

var factory = DesignPatternsDiConfig.prototypeFactory;
factory.SetPrototypes();

            factory.GetPrototype(CreationalPatterns.Prototype.Classes.ProductsEnum.ProductA);

var product = factory.GetPrototype(CreationalPatterns.Prototype.Classes.ProductsEnum.ProductA);

Console.WriteLine(product); 

where DesignPatternsDiConfig class has the following implementation (implementation extended according to the previous post):


     /// <summary>
    /// responsible for registrating and resolving types using IoC container
    /// </summary>
    public class DesignPatternsDiConfig
    {
        private static IContainer _container;
        
        // public IoC variables being used through application after being resolved
        public static DesignPatterns.CreationalPatterns.Prototype.Interfaces.IPrototypeFactory prototypeFactory;
        public static DesignPatterns.CreationalPatterns.FactoryMethod.Interfaces.IFactory factoryMethodfactory;


        /// <summary>
        /// registers and resolve delivered types according to the IoC container
        /// </summary>
        public static void RegisterTypes()
        {
            var builder = new ContainerBuilder();

            // Prototype factories registering
            builder.RegisterType<PrototypeFactory>().AsSelf();
            builder.RegisterType<PrototypeFactory>().As<DesignPatterns.CreationalPatterns.Prototype.Interfaces.IPrototypeFactory>();

            // Factory Method factories registering
            builder.RegisterType<DesignPatterns.CreationalPatterns.FactoryMethod.Factories.Factory>().AsSelf();
            builder.RegisterType<DesignPatterns.CreationalPatterns.FactoryMethod.Factories.Factory>()
                .As<DesignPatterns.CreationalPatterns.FactoryMethod.Interfaces.IFactory>();


            //
            // builds container -> important: can be performed only once within current application context's live
            //
            _container = builder.Build();

            //resolving Prototype factory
            prototypeFactory = _container.Resolve<PrototypeFactory>();

            //resolving Factory Method factory
            factoryMethodfactory = _container.Resolve<CreationalPatterns.FactoryMethod.Factories.Factory>();

        }
    }

Get back to Home Page

1 komentarz do wpisu “Wzorzec projektowy Twoim wzorcem cz.2. Prototyp / Design Pattern is your pattern pt.2. Prototype

  1. Pingback: dotnetomaniak.pl

Dodaj komentarz