czwartek, 23 maja 2019

[1] Wzorce projektowe - C# - Wzorzec Fabryka

W tym poście chciałbym opisać wzorzec projektowy fabryka (ang. Factory Method oraz Abstract Factory).

[Źródło: https://docs.microsoft.com/en-us/dotnet/]



Głównym zadaniem fabryk jest stworzenie nadrzędnej klasy odpowiedzialnej za tworzenie nowych obiektów. Przez co uzyskuje się łatwiejsze zmiany

Metoda fabrykująca (ang. Factory Method):


Jej zadaniem jest stworzenie pojedynczego obiektu. Klasa z taką metodą będzie zwracać tylko konkretny stworzy obiekt. Dodatkowo w przypadku wykorzystania metody wytwórczej klasa tworząca obiekt nie dziedziczy po innej klasie bądź interfejsie.

Metoda ta jest przydatna gdy chcemy stworzyć podobne elementy jednego typu.

Poniżej opiszę prosty przykład realizujący opisany przykład.

Na samym początku definiujemy klasę główną w której główny konstruktor będzie zdefiniowany jako prywatny.

  1. public class FactoryClass
  2. {
  3.     private UInt64 Data1;
  4.     private UInt64 Data2;
  5.     private FactoryClass(UInt64 data1, UInt64 data2)
  6.     {
  7.        this.Data1 = data1;
  8.        this.Data2 = data2;
  9.     }
  10. }

Teraz do klasy dodajemy konstruktory publiczne, które pozwolą w różny sposób stworzyć nowy obiekt klasy.

  1. public static FactoryClass newFactoryObject1(UInt64 data1, UInt64 data2)
  2. {
  3.     return new FactoryClass(data1, data2);
  4. }
  5. public static FactoryClass newFactoryObject2(UInt64 data1, UInt64 data2)
  6. {
  7.     return new FactoryClass((data1 * 10 - 7)(data1 * 7 - 10));
  8. }

Teraz w celu stworzenia nowego obiektu z zmodyfikowanymi parametrami należy wywołać:

  1. FactoryClass newFactoryClass_1 = FactoryClass.newFactoryObject1(3422138398749236);
  2. FactoryClass newFactoryClass_2 = FactoryClass.newFactoryObject1(8769834641236314);

Można także wyciągnąć metody tworzące do osobnej wewnętrznej klasy. Przez co ciągle będzie możliwość używania prywatnego konstruktora.

  1. public class FactoryClass
  2. {      
  3.     private UInt64 Data1;
  4.     private UInt64 Data2;
  5.     private FactoryClass(UInt64 data1, UInt64 data2)
  6.     {
  7.         this.Data1 = data1;
  8.         this.Data2 = data2;
  9.     }
  10.     public class FactoryCreateClass
  11.     {
  12.         public static FactoryClass newFactoryObject1(UInt64 data1, UInt64 data2)
  13.         {
  14.             return new FactoryClass(data1, data2);
  15.         }
  16.         public static FactoryClass newFactoryObject2(UInt64 data1, UInt64 data2)
  17.         {
  18.             return new FactoryClass((data1 * 10 - 7)(data1 * 7 - 10));
  19.         }
  20.     }
  21. }

Kolejny sposób wygląda nie no inaczej. Tworzona jest jedna wspólna klasa, która odpowiada za odwołanie do innych klas reprezentujących ten sam typ.

  1. public MainFactoryClass CreateFactoryType(ChildClass_Type type, UInt16 value)
  2. {
  3.     MainFactoryClass test = null;
  4.     if (type == ChildClass_Type.type1)
  5.     {
  6.         return (test = new ChildClass(value));
  7.     }
  8.     else if (type == ChildClass_Type.type2)
  9.     {
  10.         return (test = new ChildClass2(value));
  11.     }
  12.     return null;
  13. }

Klasy tworzące osobny obiekt:

  1. public class ChildClass : MainFactoryClass
  2. {
  3.     private UInt16 Val;
  4.     public ChildClass(UInt16 value)
  5.     {
  6.         this.Val = value;
  7.     }
  8. }
  9. public class ChildClass2 : MainFactoryClass
  10. {
  11.     private UInt16 Val;
  12.     public ChildClass2(UInt16 value)
  13.     {
  14.         this.Val = value;
  15.     }
  16. }

Fabryka abstrakcyjna (ang. Abstract Factory):


Fabryka abstrakcyjna działa na zasadzie wytworzenia interfejsu, który pozwala na stworzenie instancji abstrakcyjnych obiektów.

Na samym początku należy przygotować główny interfejs:

  1. public interface MainTypeInterface
  2. {
  3.     void UseFunction();
  4. }

Następnie tworzymy główne klasy danych typów:

  1. internal class KindOfMainType1 : MainTypeInterface
  2. {
  3.     public void UseFunction()
  4.     {
  5.     }
  6. }
  7. internal class KindOfMainType2 : MainTypeInterface
  8. {
  9.     public void UseFunction()
  10.     {
  11.     }
  12. }

Kolejnym elementem jest przygotowanie interfejsu wraz z klasami fabryk:

  1. public interface MainTypeInterfaceFactory
  2. {
  3.         MainTypeInterface UseFunction_2();
  4. }
  5. internal class KindOfMainType1_Factory : MainTypeInterfaceFactory
  6. {
  7.     public MainTypeInterface UseFunction_2()
  8.     {
  9.         return new KindOfMainType_1();
  10.     }
  11. }
  12. internal class KindOfMainType2_Factory : MainTypeInterfaceFactory
  13. {
  14.     public MainTypeInterface UseFunction_2()
  15.     {
  16.         return new KindOfMainType2();
  17.     }
  18. }

Dla tak przygotowanego zestawy klas przygotowujemy klasę odpowiedzialną za wytworzenie poszczególnych typów z zadanymi parametrami. Na samym początku należy zdeklarować rodzaje typów np. za pomocą enum:

  1. public enum AvailableType
  2. {
  3.    Type1, Type2
  4. }

Następnie deklarujemy zmienną tzw. słownika (Dictionary) zawierającą możliwe do wybrania typy obiektów:

  1. private Dictionary<AvailableType, MainTypeInterfaceFactory> factories =
  2.     new Dictionary<AvailableType, MainTypeInterfaceFactory>();

Konstruktor klasy ma za zadanie odczytanie zdeklarowanych typów i dodanie ich do słownika:

  1. public PrepareNewType()
  2. {
  3.     foreach (AvailableType type in Enum.GetValues(typeof(AvailableType)))
  4.     {
  5.         var factory = (MainTypeInterfaceFactory)Activator.CreateInstance(
  6.             Type.GetType("WpfThingSpeak." + "KindOfMain" + Enum.GetName(typeof(AvailableType), type) + "_Factory"
  7.          ));
  8.          factories.Add(type, factory);
  9.     }
  10. }

Poniższa funkcja ma zadanie zwrócić zdeklarowany do zainicjalizowania typ:

  1. public MainTypeInterface GetSelectedTypeNewType(AvailableType type)
  2. {
  3.     return factories[type].UseFunction_2();
  4. }

Cała klasa wygląda następująco:

  1. public class PrepareNewType
  2. {
  3.     public enum AvailableType
  4.     {
  5.         Type1, Type2
  6.     }
  7.  
  8.     private Dictionary<AvailableType, MainTypeInterfaceFactory> factories =
  9.        new Dictionary<AvailableType, MainTypeInterfaceFactory>();
  10.  
  11.     public PrepareNewType()
  12.     {
  13.         foreach (AvailableType type in Enum.GetValues(typeof(AvailableType)))
  14.         {
  15.             var factory = (MainTypeInterfaceFactory)Activator.CreateInstance(
  16.                     Type.GetType("WpfThingSpeak." + "KindOfMain" + Enum.GetName(typeof(AvailableType), type) + "_Factory"
  17.                 ));
  18.             factories.Add(type, factory);
  19.         }
  20.     }
  21.  
  22.     public MainTypeInterface GetSelectedTypeNewType(AvailableType type)
  23.     {
  24.         return factories[type].UseFunction_2();
  25.     }
  26. }