Sök…


Anmärkningar

Ge ett gränssnitt för att skapa familjer av relaterade eller beroende objekt utan att ange deras konkreta klasser.

- GOF 1994

Enkel fabrik (Java)

En fabrik minskar kopplingen mellan kod som måste skapa objekt från objektskapningskod. Objektskapning görs inte uttryckligen genom att ringa en klasskonstruktör utan genom att ringa någon funktion som skapar objektet för den som ringer. Ett enkelt Java-exempel är följande:

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

I det här exemplet ger användaren bara en aning om vad han behöver och fabriken är fri att konstruera något lämpligt. Det är ett beroende inversion: implementeraren av Car koncept är fri att återvända en lämplig betong Car begärs av användaren, vilket i sin tur inte känner till detaljerna i betongobjekt byggas.

Detta är ett enkelt exempel på hur fabriken fungerar, naturligtvis är det i det här exemplet alltid möjligt att anställa betongklasser; men man kan förhindra det genom att gömma betongklasser i ett paket, så att användaren tvingas använda fabriken.

.Net Fiddle för ovanstående exempel.

Abstrakt fabrik (C ++)

Abstrakt fabriksmönster ger ett sätt att få en sammanhängande samling av objekt genom en samling fabriksfunktioner. Som för varje mönster reduceras kopplingen genom att abstrahera hur en uppsättning objekt skapas, så att användarkoden är omedveten om de många detaljerna i de objekt han behöver.

Följande C ++ -exempel illustrerar hur man får olika typer av objekt av samma (hypotetiska) GUI-familj:

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

Om den genererade körbara filen heter abstractfactory kan utdata ge:

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

Enkelt exempel på Factory som använder en IoC (C #)

Fabriker kan också användas i samband med Inversion of Control (IoC) -bibliotek.

  • Det typiska användningsfallet för en sådan fabrik är när vi vill skapa ett objekt baserat på parametrar som inte är kända förrän körtid (som den aktuella användaren).
  • I dessa fall kan det ibland vara svårt (om inte omöjligt) att konfigurera IoC-biblioteket ensamt för att hantera denna typ av kontekstuell information om runtime, så att vi kan packa in den på en fabrik.

Exempel

  • Antag att vi har en User vars egenskaper (ID, Säkerhetsprövning nivå, etc.), är okända tills runtime (eftersom den aktuella användaren kan vara vem som helst som använder programmet).
  • Vi måste ta den aktuella ISecurityToken och få en ISecurityToken för dem, som sedan kan användas för att kontrollera om användaren får utföra vissa åtgärder eller inte.
  • Implementeringen av ISecurityToken kommer att variera beroende på användarens nivå - med andra ord, ISecurityToken använder polymorfism .

I det här fallet har vi två implementationer, som också använder markörgränssnitt för att göra det lättare att identifiera dem i IoC-biblioteket; IoC-biblioteket i detta fall består bara av och identifieras av abstraktions IContainer .

Observera också att många moderna IoC-fabriker har ursprungliga funktioner eller plugins som möjliggör automatisk skapande av fabriker samt att undvika behovet av markörgränssnitt som visas nedan; men eftersom inte alla gör det, omfattar detta exempel ett enkelt, lägsta vanliga funktionalitetskoncept.

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

Nästa kommer vi att skapa en SecurityToken fabrik, som kommer att vara beroende av vår 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>();
      }
   }
}

När vi har registrerat dem hos 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 

den konsumtiva koden kan använda den för att få rätt token vid körning:

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

På det här sättet drar vi nytta av kapslingen från fabriken och även från livscykelhanteringen från IoC-biblioteket.

En abstrakt fabrik

ange bildbeskrivning här

Följande designmönster kategoriseras som ett kreativt mönster.

En abstrakt fabrik används för att tillhandahålla ett gränssnitt för att skapa familjer med relaterade objekt, utan att specificera konkreta klasser och kan användas för att dölja plattformspecifika klasser.

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

Då skulle en leverantör / producent av något slag användas som skulle skickas information som skulle göra det möjligt att ge tillbaka rätt typ av fabriksimplementering:

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

Fabriksexempel genom att implementera Factory-metoden (Java)

Avsikt:

Definiera ett gränssnitt för att skapa ett objekt, men låt underklasser bestämma vilken klass att instansera. Factory Method låter en klass skjuta upp inställning till underklasser.

UML-diagram:

ange bildbeskrivning här

Produkt: Den definierar ett gränssnitt för de objekt som Factory-metoden skapar.

ConcreteProduct: Implementerar produktgränssnitt

Skapare: Förklarar fabriksmetoden

ConcreateCreator: Implementerar Factory-metoden för att returnera en instans av en ConcreteProduct

Problemmeddelande: Skapa en Factory of Games med Factory Methods, som definierar spelgränssnittet.

Kodavsnitt:

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

produktion:

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

Detta exempel visar en Factory genom att implementera en FactoryMethod .

  1. Game är gränssnittet för alla typer av spel. Den definierar komplex metod: createGame()

  2. Chess, Ludo, Checkers är olika varianter av spel, som ger implementering för att createGame()

  3. public Game getGame(String gameName) är FactoryMethod i IGameFactory klassen

  4. GameFactory skapar olika typer av spel i konstruktören. Den implementerar IGameFactory fabriksmetoden.

  5. spelnamn skickas som kommandoradsargument till NotStaticFactoryDemo

  6. getGame i GameFactory accepterar ett spel namn och återvänder motsvarande Game objekt.

När du ska använda:

  1. Fabrik : När du inte vill exponera objektininstieringslogik för klienten / den som ringer
  2. Abstrakt fabrik : När du vill tillhandahålla gränssnitt till familjer med relaterade eller beroende objekt utan att ange deras konkreta klasser
  3. Fabriksmetod: För att definiera ett gränssnitt för att skapa ett objekt, men låt underklasserna bestämma vilken klass att instansera

Jämförelse med andra kreativa mönster:

  1. Design börjar med Factory Method (mindre komplicerat, mer anpassningsbart, underklasser sprider sig) och utvecklas mot Abstract Factory, Prototype eller Builder (mer flexibel, mer komplex) när designern upptäcker där mer flexibilitet behövs

  2. Abstrakta fabriksklasser implementeras ofta med fabriksmetoder , men de kan också implementeras med prototyp

Referenser för vidare läsning: Sourcemaking design-designs

Flyvningsfabrik (C #)

I enkla ord:

En flygviktfabrik som för en given, redan känd nyckel alltid ger samma objekt som svar. För nya nycklar skapar förekomsten och returnerar den.

Använda fabriken:

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

Genomförande:

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

Extra anteckningar

Jag skulle rekommendera att lägga till denna lösning användningen av en IoC Container (som förklaras i ett annat exempel här) istället för att skapa dina egna nya instanser. Man kan göra det genom att lägga till en ny registrering för TResult i behållaren och sedan lösa från den (istället för dictionary i exemplet).

Fabriksmetod

Factory-metodmönstret är ett kreativt mönster som abstraherar bort objektets inställningslogik för att avkoppla klientkoden från det.

När en fabriksmetod tillhör en klass som är en implementering av ett annat fabriksmönster, som abstrakt fabrik, är det vanligtvis mer lämpligt att referera till mönstret som implementeras av den klassen snarare än fabriksmetodsmönstret.

Fabriksmetodsmönstret refereras ofta när man beskriver en fabriksmetod som tillhör en klass som inte primärt är en fabrik.

Det kan till exempel vara fördelaktigt att placera en fabriksmetod på ett objekt som representerar ett domänbegrepp om det objektet omsluter ett tillstånd som skulle förenkla skapandet av ett annat objekt. En fabriksmetod kan också leda till en design som är mer anpassad till Ubiquitous Language i ett specifikt sammanhang.

Här är ett kodexempel:

//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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow