Szukaj…


Uwagi

Zapewnij interfejs do tworzenia rodzin powiązanych lub zależnych obiektów bez określania ich konkretnych klas.

- GOF 1994

Prosta fabryka (Java)

Fabryka zmniejsza sprzężenie między kodem, który musi tworzyć obiekty z kodu tworzenia obiektu. Tworzenie obiektu nie odbywa się jawnie przez wywołanie konstruktora klasy, ale przez wywołanie funkcji, która tworzy obiekt w imieniu osoby wywołującej. Prosty przykład Java jest następujący:

interface Car {
}

public class CarFactory{
  static public Car create(String s) {
    switch (s) {
    default:
    case "us":
    case "american": return new Chrysler();
    case "de":
    case "german": return new Mercedes();
    case "jp":
    case "japanese": return new Mazda();
    }
  }
}

class Chrysler implements Car {
  public String toString() { return "Chrysler"; }
}

class Mazda implements Car {
  public String toString() { return "Mazda"; }
}

class Mercedes implements Car {
  public String toString() { return "Mercedes"; }
}

public class CarEx {
  public static void main(String args[]) {
    Car car = CarFactory.create("us");
    System.out.println(car);
  }
}

W tym przykładzie użytkownik po prostu daje wskazówkę, czego potrzebuje, a fabryka może zbudować coś odpowiedniego. Jest to inwersja zależności : realizator koncepcji Car ma swobodę zwrócenia odpowiedniego konkretnego Car żądanie użytkownika, który z kolei nie zna szczegółów zbudowanego konkretnego obiektu.

Jest to prosty przykład działania fabryki, oczywiście w tym przykładzie zawsze można utworzyć konkretne klasy; ale można temu zapobiec, ukrywając konkretne klasy w pakiecie, tak że użytkownik jest zmuszony do korzystania z fabryki.

.Net Fiddle dla powyższego przykładu.

Fabryka abstrakcyjna (C ++)

Abstrakcyjny wzór fabryczny zapewnia sposób na uzyskanie spójnego zbioru obiektów poprzez zbiór funkcji fabryk. Jak w przypadku każdego wzorca, sprzężenie jest redukowane poprzez abstrakcję sposobu tworzenia zestawu obiektów, dzięki czemu kod użytkownika nie jest świadomy wielu szczegółów potrzebnych mu obiektów.

Poniższy przykład C ++ ilustruje sposób uzyskiwania różnego rodzaju obiektów z tej samej (hipotetycznej) rodziny GUI:

#include <iostream>

/* Abstract definitions */
class GUIComponent {
public:
  virtual ~GUIComponent() = default;
  virtual void draw() const = 0;
};
class Frame  : public GUIComponent {};
class Button : public GUIComponent {};
class Label  : public GUIComponent {};

class GUIFactory {
public:
  virtual ~GUIFactory() = default;
  virtual std::unique_ptr<Frame> createFrame() = 0;
  virtual std::unique_ptr<Button> createButton() = 0;
  virtual std::unique_ptr<Label> createLabel() = 0;
  static std::unique_ptr<GUIFactory> create(const std::string& type);
};

/* Windows support */
class WindowsFactory : public GUIFactory {
private:
    class WindowsFrame : public Frame {
    public:
      void draw() const override { std::cout << "I'm a Windows-like frame" << std::endl; }
    };
    class WindowsButton : public Button {
    public:
      void draw() const override { std::cout << "I'm a Windows-like button" << std::endl; }
    };
    class WindowsLabel : public Label {
    public:
      void draw() const override { std::cout << "I'm a Windows-like label" << std::endl; }
    };
public:
  std::unique_ptr<Frame> createFrame() override { return std::make_unique<WindowsFrame>(); }
  std::unique_ptr<Button> createButton() override { return std::make_unique<WindowsButton>(); }
  std::unique_ptr<Label> createLabel() override { return std::make_unique<WindowsLabel>(); }
};

/* Linux support */
class LinuxFactory : public GUIFactory {
private:
    class LinuxFrame : public Frame {
    public:
      void draw() const override { std::cout << "I'm a Linux-like frame" << std::endl; }
    };
    class LinuxButton : public Button {
    public:
      void draw() const override { std::cout << "I'm a Linux-like button" << std::endl; }
    };
    class LinuxLabel : public Label {
    public:
      void draw() const override { std::cout << "I'm a Linux-like label" << std::endl; }
    };
public:
  std::unique_ptr<Frame> createFrame() override { return std::make_unique<LinuxFrame>(); }
  std::unique_ptr<Button> createButton() override { return std::make_unique<LinuxButton>(); }
  std::unique_ptr<Label> createLabel() override { return std::make_unique<LinuxLabel>(); }
};

std::unique_ptr<GUIFactory> GUIFactory::create(const string& type) {
  if (type == "windows") return std::make_unique<WindowsFactory>();
  return std::make_unique<LinuxFactory>();
}

/* User code */
void buildInterface(GUIFactory& factory) {
  auto frame = factory.createFrame();
  auto button = factory.createButton();
  auto label = factory.createLabel();

  frame->draw();
  button->draw();
  label->draw();
}

int main(int argc, char *argv[]) {
  if (argc < 2) return 1;
  auto guiFactory = GUIFactory::create(argv[1]);
  buildInterface(*guiFactory);
}

Jeśli wygenerowany plik wykonywalny nosi nazwę abstractfactory dane wyjściowe mogą dać:

$ ./abstractfactory windows
I'm a Windows-like frame
I'm a Windows-like button
I'm a Windows-like label
$ ./abstractfactory linux  
I'm a Linux-like frame
I'm a Linux-like button
I'm a Linux-like label

Prosty przykład fabryki korzystającej z IoC (C #)

Fabryki mogą być również używane w połączeniu z bibliotekami Inversion of Control (IoC).

  • Typowym przypadkiem użycia takiej fabryki jest sytuacja, gdy chcemy utworzyć obiekt na podstawie parametrów, które nie są znane aż do czasu wykonania (np. Bieżący użytkownik).
  • W takich przypadkach skonfigurowanie samej biblioteki IoC do obsługi tego rodzaju informacji kontekstowych środowiska wykonawczego może czasami być trudne (jeśli nie niemożliwe), abyśmy mogli ją zawinąć do fabryki.

Przykład

  • Załóżmy, że mamy klasę User , której cechy (identyfikator, poziom poświadczenia bezpieczeństwa itp.) Są nieznane do czasu uruchomienia (ponieważ bieżącym użytkownikiem może być każdy, kto korzysta z aplikacji).
  • Musimy wziąć bieżącego użytkownika i uzyskać dla niego ISecurityToken , którego można następnie użyć do sprawdzenia, czy użytkownik może wykonać określone działania, czy nie.
  • Implementacja ISecurityToken będzie się różnić w zależności od poziomu użytkownika - innymi słowy, ISecurityToken wykorzystuje polimorfizm .

W tym przypadku mamy dwie implementacje, które również wykorzystują interfejsy znaczników, aby ułatwić ich identyfikację w bibliotece IoC; biblioteka IoC w tym przypadku jest właśnie utworzona i zidentyfikowana przez abstrakcyjny IContainer .

Zauważ również, że wiele współczesnych fabryk IoC ma natywne możliwości lub wtyczki, które umożliwiają automatyczne tworzenie fabryk, a także unikają potrzeby interfejsów znaczników, jak pokazano poniżej; jednak ponieważ nie wszyscy tak robią, ten przykład dotyczy prostej, najniższej wspólnej koncepcji funkcjonalności.

//describes the ability to allow or deny an action based on PerformAction.SecurityLevel
public interface ISecurityToken
{
    public bool IsAllowedTo(PerformAction action);
}

//Marker interface for Basic permissions
public interface IBasicToken:ISecurityToken{};
//Marker interface for super permissions
public interface ISuperToken:ISecurityToken{};

//since IBasictoken inherits ISecurityToken, BasicToken can be treated as an ISecurityToken
public class BasicToken:IBasicToken
{
     public bool IsAllowedTo(PerformAction action)
     {
         //Basic users can only perform basic actions
         if(action.SecurityLevel!=SecurityLevel.Basic) return false;
         return true;
     }
}

public class SuperToken:ISuperToken
{
     public bool IsAllowedTo(PerformAction action)
     {
         //Super users can perform all actions         
         return true;
     }
}

Następnie stworzymy fabrykę SecurityToken , która weźmie jako zależność nasz IContainer

public class SecurityTokenFactory
{
   readonly IContainer _container;
   public SecurityTokenFactory(IContainer container)
   {
      if(container==null) throw new ArgumentNullException("container");
   }

   public ISecurityToken GetToken(User user)
   {
      if (user==null) throw new ArgumentNullException("user);
      //depending on the user security level, we return a different type; however all types implement ISecurityToken so the factory can produce them.
      switch user.SecurityLevel
      {
          case Basic:
           return _container.GetInstance<BasicSecurityToken>();
          case SuperUser:
           return _container.GetInstance<SuperUserToken>();
      }
   }
}

Po zarejestrowaniu ich w IContainer :

IContainer.For<SecurityTokenFactory>().Use<SecurityTokenFactory>().Singleton(); //we only need a single instance per app
IContainer.For<IBasicToken>().Use<BasicToken>().PerRequest(); //we need an instance per-request
IContainer.For<ISuperToken>().Use<SuperToken>().PerRequest();//we need an instance per-request 

kod używający może go użyć, aby uzyskać poprawny token w czasie wykonywania:

readonly SecurityTokenFactory _tokenFactory;
...
...
public void LogIn(User user)
{
    var token = _tokenFactory.GetToken(user);
    user.SetSecurityToken(token);
}

W ten sposób korzystamy z enkapsulacji dostarczanej przez fabrykę, a także z zarządzania cyklem życia zapewnianym przez bibliotekę IoC.

Fabryka abstrakcyjna

wprowadź opis zdjęcia tutaj

Poniższy wzorzec projektowy jest sklasyfikowany jako wzorzec kreacyjny.

Fabryka abstrakcyjna służy do tworzenia interfejsu do tworzenia rodzin powiązanych obiektów bez określania konkretnych klas i może służyć do ukrywania klas specyficznych dla platformy.

interface Tool {
    void use();
}

interface ToolFactory {
    Tool create();
}

class GardenTool implements Tool {

    @Override
    public void use() {
         // Do something...
    }
}

class GardenToolFactory implements ToolFactory {

    @Override
    public Tool create() {
        // Maybe additional logic to setup...
        return new GardenTool();
    }
}

class FarmTool implements Tool {

    @Override
    public void use() {
        // Do something...
    }
}

class FarmToolFactory implements ToolFactory {

    @Override
    public Tool create() {
        // Maybe additional logic to setup...
        return new FarmTool();
    }
}

Następnie wykorzystano by jakiegoś dostawcę / producenta, któremu przekazano by informacje, które pozwoliłyby mu zwrócić prawidłowy typ wdrożenia fabrycznego:

public final class FactorySupplier {

    // The supported types it can give you...
    public enum Type {
        FARM, GARDEN
    };

    private FactorySupplier() throws IllegalAccessException {
        throw new IllegalAccessException("Cannot be instantiated");
    }

    public static ToolFactory getFactory(Type type) {

        ToolFactory factory = null;

        switch (type) {
        case FARM:
            factory = new FarmToolFactory();
            break;
        case GARDEN:
            factory = new GardenToolFactory();
            break;
        } // Could potentially add a default case to handle someone passing in null

        return factory;
    }
}

Przykład fabryki poprzez wdrożenie metody Factory (Java)

Zamiar:

Zdefiniuj interfejs do tworzenia obiektu, ale pozwól, aby podklasy zdecydowały, którą klasę utworzyć. Metoda fabryczna pozwala klasie odroczyć tworzenie instancji do podklas.

Diagram UML:

wprowadź opis zdjęcia tutaj

Produkt: Definiuje interfejs obiektów tworzonych przez metodę Factory.

ConcreteProduct: Implementuje interfejs produktu

Twórca: Deklaruje metodę Factory

ConcreateCreator: implementuje metodę Factory, aby zwrócić instancję ConcreteProduct

Stwierdzenie problemu: Utwórz fabrykę gier, korzystając z metod fabrycznych, które definiują interfejs gry.

Fragment kodu:

import java.util.HashMap;


/* Product interface as per UML diagram */
interface Game{
    /* createGame is a complex method, which executes a sequence of game steps */
    public void createGame();
}

/* ConcreteProduct implementation as per UML diagram */
class Chess implements Game{
    public Chess(){
        createGame();
    }
    public void createGame(){
        System.out.println("---------------------------------------");
        System.out.println("Create Chess game");
        System.out.println("Opponents:2");
        System.out.println("Define 64 blocks");
        System.out.println("Place 16 pieces for White opponent");
        System.out.println("Place 16 pieces for Black opponent");
        System.out.println("Start Chess game");
        System.out.println("---------------------------------------");
    }
}
class Checkers implements Game{
    public Checkers(){
        createGame();
    }
    public void createGame(){
        System.out.println("---------------------------------------");
        System.out.println("Create Checkers game");
        System.out.println("Opponents:2 or 3 or 4 or 6");
        System.out.println("For each opponent, place 10 coins");
        System.out.println("Start Checkers game");
        System.out.println("---------------------------------------");
    }
}
class Ludo implements Game{
    public Ludo(){
        createGame();
    }
    public void createGame(){
        System.out.println("---------------------------------------");
        System.out.println("Create Ludo game");
        System.out.println("Opponents:2 or 3 or 4");
        System.out.println("For each opponent, place 4 coins");
        System.out.println("Create two dices with numbers from 1-6");
        System.out.println("Start Ludo game");
        System.out.println("---------------------------------------");
    }
}

/* Creator interface as per UML diagram */
interface IGameFactory {
    public Game getGame(String gameName);
}

/* ConcreteCreator implementation as per UML diagram */
class GameFactory implements IGameFactory {
        
    HashMap<String,Game> games = new HashMap<String,Game>();
    /*  
        Since Game Creation is complex process, we don't want to create game using new operator every time.
        Instead we create Game only once and store it in Factory. When client request a specific game, 
        Game object is returned from Factory instead of creating new Game on the fly, which is time consuming
    */
    
    public GameFactory(){
        
        games.put(Chess.class.getName(),new Chess());
        games.put(Checkers.class.getName(),new Checkers());
        games.put(Ludo.class.getName(),new Ludo());        
    }
    public Game getGame(String gameName){
        return games.get(gameName);
    }
}

public class NonStaticFactoryDemo{
    public static void main(String args[]){
        if ( args.length < 1){
            System.out.println("Usage: java FactoryDemo gameName");
            return;
        }
     
        GameFactory factory = new GameFactory();
        Game game = factory.getGame(args[0]);
        System.out.println("Game="+game.getClass().getName());
    }
}

wynik:

java NonStaticFactoryDemo Chess
---------------------------------------
Create Chess game
Opponents:2
Define 64 blocks
Place 16 pieces for White opponent
Place 16 pieces for Black opponent
Start Chess game
---------------------------------------
---------------------------------------
Create Checkers game
Opponents:2 or 3 or 4 or 6
For each opponent, place 10 coins
Start Checkers game
---------------------------------------
---------------------------------------
Create Ludo game
Opponents:2 or 3 or 4
For each opponent, place 4 coins
Create two dices with numbers from 1-6
Start Ludo game
---------------------------------------
Game=Chess

Ten przykład pokazuje klasę Factory poprzez implementację FactoryMethod .

  1. Game jest interfejsem dla wszystkich rodzajów gier. Definiuje złożoną metodę: createGame()

  2. Chess, Ludo, Checkers to różne warianty gier, które zapewniają implementację do createGame()

  3. public Game getGame(String gameName) jest FactoryMethod w IGameFactory klasie

  4. GameFactory wcześniej tworzy różne typy gier w konstruktorze. Implementuje fabryczną metodę IGameFactory .

  5. Nazwa gry jest przekazywana jako argument wiersza poleceń do NotStaticFactoryDemo

  6. getGame w GameFactory przyjmuje nazwę gry i zwraca odpowiedni obiekt Game .

Kiedy użyć:

  1. Fabryka : Gdy nie chcesz udostępniać logiki tworzenia instancji obiektu klientowi / rozmówcy
  2. Fabryka abstrakcyjna : gdy chcesz zapewnić interfejs dla rodzin powiązanych lub zależnych obiektów bez określania ich konkretnych klas
  3. Metoda fabryczna: Aby zdefiniować interfejs do tworzenia obiektu, ale pozwól, aby podklasy zdecydowały, którą klasę utworzyć

Porównanie z innymi wzorami kreacyjnymi:

  1. Projektowanie zaczyna się od metody fabrycznej (mniej skomplikowane, bardziej konfigurowalne, podklasy rozprzestrzeniają się) i ewoluuje w kierunku fabryki abstrakcyjnej, prototypu lub konstruktora (bardziej elastyczne, bardziej złożone), gdy projektant odkrywa, gdzie potrzebna jest większa elastyczność

  2. Abstrakcyjne klasy Factory są często implementowane za pomocą metod fabrycznych , ale można je również zaimplementować przy użyciu Prototypu

Odniesienia do dalszej lektury: Wzory projektowania zaopatrzenia

Flyweight Factory (C #)

W prostych słowach:

Fabryka Flyweight, która dla danego, znanego już klucza, zawsze da ten sam obiekt co odpowiedź. W przypadku nowych kluczy utwórz instancję i zwróć ją.

Korzystanie z fabryki:

ISomeFactory<string, object> factory = new FlyweightFactory<string, object>();

var result1 = factory.GetSomeItem("string 1");
var result2 = factory.GetSomeItem("string 2");
var result3 = factory.GetSomeItem("string 1");

//Objects from different keys
bool shouldBeFalse = result1.Equals(result2);

//Objects from same key
bool shouldBeTrue = result1.Equals(result3);

Realizacja:

public interface ISomeFactory<TKey,TResult> where TResult : new()
{
    TResult GetSomeItem(TKey key);
}

public class FlyweightFactory<TKey, TResult> : ISomeFactory<TKey, TResult> where TResult : new()
{
    public TResult GetSomeItem(TKey key)
    {
        TResult result;
        if(!Mapping.TryGetValue(key, out result))
        {
            result = new TResult();
            Mapping.Add(key, result);
        }
        return result;
    }

    public Dictionary<TKey, TResult> Mapping { get; set; } = new Dictionary<TKey, TResult>();
}

Dodatkowe uwagi

Zalecam dodanie do tego rozwiązania użycia IoC Container (jak wyjaśniono w innym przykładzie tutaj) zamiast tworzenia własnych nowych instancji. Można to zrobić, dodając nową rejestrację TResult do kontenera, a następnie rozwiązując z niego (zamiast dictionary w przykładzie).

Metoda fabryczna

Wzorzec metody Factory jest wzorcem kreacyjnym, który wyodrębnia logikę tworzenia instancji obiektu w celu oddzielenia od niego kodu klienta.

Kiedy metoda fabryczna należy do klasy, która jest implementacją innego wzorca fabrycznego, takiego jak Fabryka abstrakcyjna , zwykle bardziej odpowiednie jest odwołanie się do wzorca zaimplementowanego przez tę klasę, zamiast wzorca metody Fabrycznej.

Wzorzec metody fabrycznej jest częściej przywoływany przy opisywaniu metody fabrycznej, która należy do klasy, która nie jest przede wszystkim fabryką.

Na przykład może być korzystne umieszczenie metody fabrycznej na obiekcie reprezentującym koncepcję domeny, jeśli obiekt ten zawiera pewien stan, który uprościłby proces tworzenia innego obiektu. Metoda fabryczna może również prowadzić do projektu bardziej dopasowanego do Wszechobecnego języka określonego kontekstu.

Oto przykład kodu:

//Without a factory method
Comment comment = new Comment(authorId, postId, "This is a comment");

//With a factory method
Comment comment = post.comment(authorId, "This is a comment");


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow