Поиск…


замечания

Предоставить интерфейс для создания семейств связанных или зависимых объектов без указания их конкретных классов.

- ГОФ 1994

Простая фабрика (Java)

Завод уменьшает связь между кодом, который должен создавать объекты из кода создания объекта. Создание объекта не производится явно, вызывая конструктор класса, но вызывая некоторую функцию, которая создает объект от имени вызывающего. Простой пример Java следующий:

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);
  }
}

В этом примере пользователь просто дает некоторый намек на то, что ему нужно, и фабрика может свободно создавать что-то подходящее. Это инверсия зависимостей : разработчик концепции Car может свободно возвращать соответствующий конкретный Car запрошенный пользователем, который, в свою очередь, не знает деталей конкретного объекта, построенного.

Это простой пример того, как работает фабрика, конечно, в этом примере всегда можно создавать конкретные классы; но можно предотвратить его, скрывая конкретные классы в пакете, так что пользователь вынужден использовать фабрику.

.Net Fiddle для примера выше.

Абстрактная фабрика (C ++)

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

Следующий пример C ++ иллюстрирует, как получить различные типы объектов одного и того же (гипотетического) семейства 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);
}

Если сгенерированный исполняемый файл называется abstractfactory то вывод может выдавать:

$ ./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

Простой пример Factory, который использует IoC (C #)

Фабрики могут использоваться совместно с библиотеками инверсии управления (IoC).

  • Типичным примером использования такой фабрики является то, что мы хотим создать объект на основе параметров, которые неизвестны до времени выполнения (например, текущего пользователя).
  • В этих случаях иногда бывает сложно (если не невозможно) сконфигурировать только библиотеку IoC для обработки такого рода контекстной информации времени выполнения, поэтому мы можем обернуть ее на заводе.

пример

  • Предположим, что у нас есть класс User , характеристики которого (ID, уровень безопасности и т. Д.) Неизвестны до времени исполнения (поскольку текущий пользователь может быть любым, кто использует приложение).
  • Нам нужно взять текущего пользователя и получить для него ISecurityToken , который затем может использоваться для проверки того, разрешено ли пользователю выполнять определенные действия или нет.
  • Реализация ISecurityToken будет зависеть от уровня пользователя - другими словами, ISecurityToken использует полиморфизм .

В этом случае мы имеем две реализации, которые также используют интерфейсы Marker, чтобы упростить их идентификацию в библиотеке IoC; библиотека IoC в этом случае просто составлена ​​и идентифицирована абстракцией IContainer .

Отметим также, что многие современные заводы IoC имеют собственные возможности или плагины, которые позволяют автоматически создавать заводы, а также избегать необходимости в интерфейсах маркеров, как показано ниже; однако, поскольку не все это делают, этот пример удовлетворяет простой, самой низкой общей концепции функциональности.

//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;
     }
}

Затем мы создадим фабрику SecurityToken , которая будет зависеть от нашего 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>();
      }
   }
}

Как только мы зарегистрировали их с 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 

потребительский код может использовать его для получения правильного токена во время выполнения:

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

Таким образом, мы получаем выгоду от инкапсуляции, предоставляемой заводом, а также из управления жизненным циклом, предоставляемого библиотекой IoC.

Абстрактная фабрика

введите описание изображения здесь

Следующий шаблон проектирования классифицируется как шаблон создания.

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

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();
    }
}

Затем будет использоваться поставщик / производитель какого-то рода, который будет передавать информацию, которая позволила бы ему вернуть правильный тип заводской реализации:

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;
    }
}

Пример фабрики, реализуя метод Factory (Java)

Намерение:

Определите интерфейс для создания объекта, но пусть подкласс определяет, какой класс следует создавать. Factory Method позволяет кластеру отложить создание экземпляров подклассов.

Диаграмма UML:

введите описание изображения здесь

Продукт: Определяет интерфейс объектов, создаваемых методом Factory.

ConcreteProduct: реализует интерфейс продукта

Создатель: объявляет заводский метод

ConcreateCreator: реализует метод Factory для возврата экземпляра ConcreteProduct

Заявление о проблемах: создайте фабрику игр с помощью Factory Methods, которая определяет интерфейс игры.

Фрагмент кода:

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());
    }
}

выход:

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

В этом примере показан класс Factory , реализующий FactoryMethod .

  1. Game - это интерфейс для всех типов игр. Он определяет сложный метод: createGame()

  2. Chess, Ludo, Checkers - это разные варианты игр, которые обеспечивают реализацию createGame()

  3. public Game getGame(String gameName) - FactoryMethod в классе IGameFactory

  4. GameFactory предварительно создает разные типы игр в конструкторе. Он реализует заводской метод IGameFactory .

  5. Название игры передается как аргумент командной строки NotStaticFactoryDemo

  6. getGame в GameFactory принимает имя игры и возвращает соответствующий Game объект.

Когда использовать:

  1. Factory : Если вы не хотите выставлять логику создания объекта для клиента / вызывающего абонента
  2. Абстрактная фабрика : если вы хотите предоставить интерфейс для семей связанных или зависимых объектов, не указав их конкретные классы
  3. Фабричный метод: определить интерфейс для создания объекта, но пусть подклассы решают, какой класс следует создавать

Сравнение с другими шаблонами создания:

  1. Дизайн начинается с использования Factory Method (менее сложные, настраиваемые, подклассы размножаются) и эволюционирует в направлении Abstract Factory, Prototype или Builder (более гибкий, более сложный), поскольку разработчик обнаруживает, что требуется больше гибкости

  2. Абстрактные классы Factory часто реализуются с помощью Factory Methods , но они также могут быть реализованы с использованием Prototype

Ссылки для дальнейшего чтения: Sourcemaking design-patterns

Flyweight Factory (C #)

Простыми словами:

Фабрика мухи, которая для данного, уже известного, ключа всегда будет давать тот же объект, что и ответ. Для новых ключей создаст экземпляр и вернет его.

Использование фабрики:

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);

Реализация:

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>();
}

Дополнительные примечания

Я бы рекомендовал добавить к этому решению использование IoC Container (как описано в другом примере здесь) вместо создания ваших собственных новых экземпляров. Это можно сделать, добавив новую регистрацию для TResult в контейнер и затем разрешив его (вместо dictionary в примере).

Заводской метод

Шаблон метода Factory - это шаблон создания, который абстрагирует логику создания экземпляра объекта, чтобы отделить клиентский код от него.

Когда заводский метод принадлежит классу, который представляет собой реализацию другого заводского шаблона, такого как абстрактная фабрика, тогда обычно более целесообразно ссылаться на шаблон, реализованный этим классом, а не на шаблон Factory.

Образец метода Factory чаще упоминается при описании фабричного метода, который принадлежит классу, который не является главным образом фабрикой.

Например, может оказаться целесообразным разместить фабричный метод для объекта, представляющего концепцию домена, если этот объект инкапсулирует какое-либо состояние, которое упростило бы процесс создания другого объекта. Метод фабрики также может привести к дизайну, который больше соответствует Ubiquitous Language определенного контекста.

Вот пример кода:

//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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow