Design patterns
Fabrik
Suche…
Bemerkungen
Bieten Sie eine Schnittstelle zum Erstellen von Familien verwandter oder abhängiger Objekte, ohne deren konkrete Klassen anzugeben.
- GOF 1994
Einfache Fabrik (Java)
Eine Factory verringert die Kopplung zwischen Code, der Objekte aus Objekterstellungscode erstellen muss. Die Objekterstellung erfolgt nicht explizit durch Aufrufen eines Klassenkonstruktors, sondern durch Aufrufen einer Funktion, die das Objekt im Namen des Aufrufers erstellt. Ein einfaches Java-Beispiel ist das folgende:
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);
}
}
In diesem Beispiel gibt der Benutzer lediglich einen Hinweis darauf, was er braucht, und die Fabrik kann etwas Passendes konstruieren. Es ist eine Abhängigkeitsinversion : Der Implementierer von Car
concept kann ein geeignetes, vom Benutzer angefordertes, konkretes Car
, das wiederum die Details des konkreten Objekts nicht kennt.
Dies ist ein einfaches Beispiel für die Funktionsweise von Factory. In diesem Beispiel ist es natürlich immer möglich, konkrete Klassen zu instanziieren. Dies kann jedoch verhindert werden, indem konkrete Klassen in einem Paket ausgeblendet werden, sodass der Benutzer die Factory zwingend verwenden muss.
.Net Geige für obiges Beispiel.
Abstrakte Fabrik (C ++)
Das abstrakte Fabrikmuster bietet eine Möglichkeit, eine zusammenhängende Sammlung von Objekten durch eine Sammlung von Funktionen der Fabrik zu erhalten. Wie bei jedem Muster wird die Kopplung reduziert, indem die Art und Weise, wie eine Gruppe von Objekten erstellt wird, abstrahiert wird, sodass der Benutzercode die vielen Details der Objekte, die er benötigt, nicht kennt.
Das folgende C ++ - Beispiel veranschaulicht, wie verschiedene Arten von Objekten derselben (hypothetischen) GUI-Familie abgerufen werden:
#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);
}
Wenn die generierte ausführbare Datei abstractfactory
heißt, kann die Ausgabe möglicherweise Folgendes ergeben:
$ ./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
Einfaches Beispiel für Factory, die einen IoC (C #) verwendet
Factories können auch in Verbindung mit Inversion of Control (IoC) -Bibliotheken verwendet werden.
- Der typische Anwendungsfall für eine solche Factory ist, wenn wir ein Objekt basierend auf Parametern erstellen möchten, die erst zur Laufzeit bekannt sind (z. B. der aktuelle Benutzer).
- In diesen Fällen kann es manchmal schwierig (wenn nicht unmöglich) sein, die IoC-Bibliothek allein für diese Art von kontextuellen Laufzeitinformationen zu konfigurieren, sodass wir sie in einer Factory einpacken können.
Beispiel
- Angenommen, wir haben eine
User
, deren Eigenschaften (ID, Sicherheitsfreigabeebene usw.) bis zur Laufzeit unbekannt sind (da der aktuelle Benutzer möglicherweise jeder ist, der die Anwendung verwendet). - Wir müssen den aktuellen Benutzer nehmen und ein
ISecurityToken
für ihn erhalten, mit dem überprüft werden kann, ob der Benutzer bestimmte Aktionen ausführen darf oder nicht. - Die Implementierung von ISecurityToken hängt von der Stufe des Benutzers ab. Mit anderen Worten, ISecurityToken verwendet Polymorphismus .
In diesem Fall gibt es zwei Implementierungen, bei denen auch Marker-Interfaces verwendet werden , um die Identifikation der IoC-Bibliothek zu erleichtern. Die IoC-Bibliothek wird in diesem Fall nur durch den Abstraktions- IContainer
erstellt und identifiziert.
Beachten Sie auch, dass viele moderne IoC-Fabriken über native Funktionen oder Plugins verfügen, die die automatische Erstellung von Fabriken ermöglichen und die Verwendung von Marker-Schnittstellen (siehe unten) vermeiden. Da jedoch nicht alle dies tun, ist dieses Beispiel auf ein einfaches, niedrigstes allgemeines Funktionskonzept ausgerichtet.
//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;
}
}
Als nächstes werden wir eine SecurityToken
Factory erstellen, die unseren IContainer
als Abhängigkeit 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>();
}
}
}
Sobald wir diese beim IContainer
registriert 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
Der konsumierende Code kann es verwenden, um zur Laufzeit das richtige Token zu erhalten:
readonly SecurityTokenFactory _tokenFactory;
...
...
public void LogIn(User user)
{
var token = _tokenFactory.GetToken(user);
user.SetSecurityToken(token);
}
Auf diese Weise profitieren wir von der Verkapselung des Werks sowie vom Lifecycle-Management der IoC-Bibliothek.
Eine abstrakte Fabrik
Das folgende Entwurfsmuster wird als kreatives Muster kategorisiert.
Eine abstrakte Factory stellt eine Schnittstelle zum Erstellen von Familien verwandter Objekte bereit, ohne konkrete Klassen anzugeben, und kann zum Ausblenden plattformspezifischer Klassen verwendet werden.
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();
}
}
Dann würde ein Lieferant / Hersteller verwendet, der Informationen weitergeben würde, die es ihm ermöglichen würden, die korrekte Art der Fabrikumsetzung wiederzugeben:
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-Beispiel durch Implementierung der Factory-Methode (Java)
Absicht:
Definieren Sie eine Schnittstelle zum Erstellen eines Objekts. Lassen Sie die Unterklassen entscheiden, welche Klasse instanziiert werden soll. Mit der Factory-Methode kann eine Klasse die Instantiierung auf Unterklassen verschieben.
UML-Diagramm:
Produkt: Definiert eine Schnittstelle der Objekte, die die Factory-Methode erstellt.
ConcreteProduct: Implementiert die Produktschnittstelle
Creator: Deklariert die Factory-Methode
ConcreateCreator: Implementiert die Factory-Methode, um eine Instanz eines ConcreteProduct zurückzugeben
Problemstellung: Erstellen Sie eine Factory of Games mit Factory Methods, die die Spieloberfläche definiert.
Code-Auszug:
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());
}
}
Ausgabe:
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
Dieses Beispiel zeigt eine Factory
Klasse durch Implementieren einer FactoryMethod
.
Game
ist die Schnittstelle für alle Arten von Spielen. Es definiert eine komplexe Methode:createGame()
Chess, Ludo, Checkers
sind verschiedene Spielvarianten, die die Implementierung voncreateGame()
public Game getGame(String gameName)
istFactoryMethod
in der KlasseIGameFactory
GameFactory
erstellt verschiedene Arten von Spielen im Konstruktor vor. Es implementiert dieIGameFactory
Factory-Methode.Der
NotStaticFactoryDemo
wird als Befehlszeilenargument anNotStaticFactoryDemo
getGame
inGameFactory
nimmt einen Spielnamen und gibt entsprechendesGame
- Objekt.
Wann verwenden:
- Factory : Wenn Sie keine Objekt-Instantiierungslogik für den Client / Anrufer verfügbar machen möchten
- Abstract Factory : Wenn Sie eine Schnittstelle für Familien verwandter oder abhängiger Objekte bereitstellen möchten, ohne deren konkrete Klassen anzugeben
- Factory-Methode: Eine Schnittstelle zum Erstellen eines Objekts definieren, die Unterklassen entscheiden lassen, welche Klasse instanziiert werden soll
Vergleich mit anderen Kreationsmustern:
Das Design beginnt mit der Factory-Methode (weniger kompliziert, anpassbarer, die Unterklassen breiten sich aus) und entwickelt sich zu Abstract Factory, Prototype oder Builder (flexibler, komplexer), während der Designer erkennt, wo mehr Flexibilität erforderlich ist
Zusammenfassung Factory- Klassen werden häufig mit Factory-Methoden implementiert, sie können jedoch auch mit Prototype implementiert werden
Literaturhinweise: Sourcemaking-Designmuster
Fliegengewicht Fabrik (C #)
In einfachen Worten:
Eine Flyweight-Fabrik , die für einen bestimmten, bereits bekannten Schlüssel immer dasselbe Objekt liefert wie die Antwort. Bei neuen Schlüsseln wird die Instanz erstellt und zurückgegeben.
Mit der fabrik:
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);
Implementierung:
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>();
}
Zusätzliche Hinweise
Ich würde empfehlen, zu dieser Lösung die Verwendung eines IoC Container
(wie in einem anderen Beispiel erläutert) hinzuzufügen, anstatt eigene neue Instanzen zu erstellen. Sie können dies tun, indem TResult
dem Container eine neue Registrierung für das TResult
hinzufügen und diese dann auflösen (anstelle des dictionary
im Beispiel).
Fabrikmethode
Das Factory-Methodenmuster ist ein kreatives Muster, das die Instanziierungslogik eines Objekts abstrahiert, um den Clientcode von ihm zu entkoppeln.
Wenn eine Factory-Methode zu einer Klasse gehört, bei der es sich um eine Implementierung eines anderen Factory-Musters handelt, z. B. einer Abstract-Factory, ist es in der Regel besser, auf das von dieser Klasse implementierte Muster als auf das Factory-Methodenmuster zu verweisen.
Auf das Factory-Methodenmuster wird häufiger Bezug genommen, wenn eine Factory-Methode beschrieben wird, die zu einer Klasse gehört, die nicht in erster Linie eine Factory ist.
Zum Beispiel kann es vorteilhaft sein, eine Factory-Methode auf einem Objekt zu platzieren, das ein Domänenkonzept darstellt, wenn dieses Objekt einen Zustand einkapselt, der den Erstellungsprozess eines anderen Objekts vereinfacht. Eine Factory-Methode kann auch zu einem Design führen, das mehr auf die universelle Sprache eines bestimmten Kontexts abgestimmt ist.
Hier ist ein Codebeispiel:
//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");