2015-11-23 13 views
8

Ich bin auf der Suche nach einem einfachen Beispiel, wie eine Factory-Klasse zu implementieren, aber ohne die Verwendung eines Schalters oder eine Wenn-Dann-Anweisung. Alle Beispiele, die ich finden kann, benutzen eins. Zum Beispiel, wie könnte man dieses einfache Beispiel ändern (unten), so dass die tatsächliche Fabrik nicht vom Switch abhängig ist? Es scheint mir, dass dieses Beispiel das Open/Close-Prinzip verletzt. Ich möchte in der Lage sein, konkrete Klassen ('Manager', 'Clerk', 'Programmer' usw.) hinzuzufügen, ohne die Factory-Klasse ändern zu müssen.Factory Pattern ohne einen Schalter oder wenn/dann

Danke!

class Program 
{ 
    abstract class Position 
    { 
     public abstract string Title { get; } 
    } 

    class Manager : Position 
    { 
     public override string Title 
     { 
      get { return "Manager"; } 
     } 
    } 

    class Clerk : Position 
    { 
     public override string Title 
     { 
      get { return "Clerk"; } 
     } 
    } 

    class Programmer : Position 
    { 
     public override string Title 
     { 
      get { return "Programmer"; } 
     } 
    } 

    static class Factory 
    { 
     public static Position Get(int id) 
     { 
      switch (id) 
      { 
       case 0: return new Manager(); 
       case 1: return new Clerk(); 
       case 2: return new Programmer(); 
       default: return new Programmer(); 
      } 
     } 
    } 

    static void Main(string[] args) 
    { 
     for (int i = 0; i <= 2; i++) 
     { 
      var position = Factory.Get(i); 
      Console.WriteLine("Where id = {0}, position = {1} ", i, position.Title); 
     } 
     Console.ReadLine(); 
    } 
} 

UPDATE:

Wow! Danke allen! Ich habe eine Tonne gelernt. Nachdem ich alle Rückmeldungen erhalten hatte, mischte ich einige der Antworten ein und kam auf diese Idee. Ich wäre offen für einen weiteren Dialog über einen besseren Weg, dies zu tun.

class Program 
{ 

    public interface IPosition 
    { 
     string Title { get; } 
    } 

    class Manager : IPosition 
    { 
     public string Title 
     { 
      get { return "Manager"; } 
     } 
    } 

    class Clerk : IPosition 
    { 
     public string Title 
     { 
      get { return "Clerk"; } 
     } 
    } 

    class Programmer : IPosition 
    { 
     public string Title 
     { 
      get { return "Programmer"; } 
     } 
    } 

static class PositionFactory 
{ 
    public static T Create<T>() where T : IPosition, new() 
    { 
     return new T(); 
    } 
} 


static void Main(string[] args) 
    { 

     IPosition position0 = PositionFactory.Create<Manager>(); 
     Console.WriteLine("0: " + position0.Title); 

     IPosition position1 = PositionFactory.Create<Clerk>(); 
     Console.WriteLine("1: " + position1.Title); 

     IPosition position2 = PositionFactory.Create<Programmer>(); 
     Console.WriteLine("1: " + position2.Title); 

     Console.ReadLine(); 
    } 
} 

Ein weiterer Edit:

Es ist auch möglich, eine Instanz der Schnittstelle mit Hilfe eines unbekannten Typs zu erstellen:

static class PositionFactory 
{ 
    public static IPosition Create(string positionName) 
    {  
     Type type = Type.GetType(positionName); 
     return (IPosition)Activator.CreateInstance(type); 
    } 
} 

die dann wie folgt genannt werden:

IPosition position = PositionFactory.Create("Manager"); 
Console.WriteLine(position.Title); 
+4

Sie am [Abstract Factory Pattern] einen Blick darauf werfen konnte (https: // en.wikipedia.org/wiki/Abstract_factory_pattern) und verwenden Sie die Abhängigkeitsinjektion, um die richtige Fabrik für den Job zu übergeben. – Adimeus

+0

Ich würde so etwas wie Ninject oder Autofac empfehlen – tdbeckett

+0

Dies ist ein klassischer Fall der Abhängigkeitsinjektion. Die einfachste Verwendung eines IoC-Containers (Unity, Ninject, etc ...) besteht darin, ihn genau als eine verklärte Fabrik zu verwenden. – Tanuki

Antwort

4

Wie wäre es damit (kein Wörterbuch erforderlich und beachten Sie, dass Sie einen Syntaxfehler, wenn Ihr Versuch Create<Position>() erhalten):

EDIT - Aktualisiert, um eine explizit implementierte IPosition-Schnittstelle zu verwenden. Nur Instanzen von IPosition können auf die Elementfunktionen zugreifen (z. B. <implementation of Manager>.Title wird nicht kompiliert).

EDIT # 2 Factory.Create sollte eine IPosition nicht T zurückgeben, wenn die Schnittstelle richtig verwendet wird.

using System; 
using System.Collections.Generic; 

class Program 
{ 
    interface IPosition 
    { 
     string Title { get; } 
     bool RequestVacation(); 
    } 

    class Manager : IPosition 
    { 
     string IPosition.Title 
     { 
      get { return "Manager"; } 
     } 

     bool IPosition.RequestVacation() 
     { 
      return true; 
     } 
    } 

    class Clerk : IPosition 
    { 
     int m_VacationDaysRemaining = 1; 

     string IPosition.Title 
     { 
      get { return "Clerk"; } 
     } 

     bool IPosition.RequestVacation() 
     { 
      if (m_VacationDaysRemaining <= 0) 
      { 
       return false; 
      } 
      else 
      { 
       m_VacationDaysRemaining--; 
       return true; 
      } 
     } 
    } 

    class Programmer : IPosition 
    { 
     string IPosition.Title 
     { 
      get { return "Programmer"; } 
     } 

     bool IPosition.RequestVacation() 
     { 
      return false; 
     } 
    } 

    static class Factory 
    { 
     public static IPosition Create<T>() where T : IPosition, new() 
     { 
      return new T(); 
     } 
    } 

    static void Main(string[] args) 
    { 
     List<IPosition> positions = new List<IPosition>(3); 
     positions.Add(Factory.Create<Manager>()); 
     positions.Add(Factory.Create<Clerk>()); 
     positions.Add(Factory.Create<Programmer>()); 

     foreach (IPosition p in positions) { Console.WriteLine(p.Title); } 
     Console.WriteLine(); 

     Random rnd = new Random(0); 
     for (int i = 0; i < 10; i++) 
     { 
      int index = rnd.Next(3); 
      Console.WriteLine("Title: {0}, Request Granted: {1}", positions[index].Title, positions[index].RequestVacation()); 
     } 

     Console.ReadLine(); 
    } 
} 
+0

Ich mag die Einfachheit dieser sehr. Aber wenn ich es ausprobiere, bekomme ich eine leere Konsole. Lass mich das ein bisschen verdauen. –

+0

Dort gibt es keinen Code, um den Inhalt von Positionen auszugeben. Fügen Sie einfach eine foreach hinzu (Position p in Positionen) ... –

+0

Wie ordnen Sie die ID-Nummern zu _new_ Instanzen? Ich sehe, dass man eine Instanz von "Positionen" nach Index ziehen kann, was ungefähr den IDs entspricht, aber was ist, wenn Sie mehrere * separate * Instanzen eines 'Managers 'benötigen? –

5

Sie könnten benutzerdefinierte Attribute und reflec verwenden tion.

[PositionType(1)] 
class Manager : Position 
{ 
    public override string Title 
    { 
     get 
     { return "Manager"; } 
    } 
} 

[PositionType(2)] 
class Clerk : Position 
{ 
    public override string Title 
    { 
     get 
     { return "Clerk"; } 
    } 
} 

In Ihrem Werk konnte man dann alle Klassen erhalten, die von Position erben und finden Sie das eine, die die PositionType Attribut mit dem richtigen Wert hat.

static class Factory 
{ 
    public static Position Get(int id) 
    { 
     var types = typeof(Position).Assembly.GetTypes() 
      .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(Position))) 
      .ToList(); 

     Position position = null; 
     foreach(var type in types) 
     { 
      type.GetCustomAttributes<PositionTypeAttribute>(); 

      if(type.PositionId == id) 
      { 
       position = Activator.CreateInstance(type) as Position; 
       break; 
      } 
     } 

     if(position == null) 
     { 
      var message = $"Could not find a Position to create for id {id}."; 
      throw new NotSupportedException(message); 
     } 

     return position; 
    } 
} 
+0

Ok, ich kann das funktionieren sehen. Aber Reflexion scheint wie Overkill? Ich mag den obigen Vorschlag von Adimeus, das Abstract Factory Pattern zu verwenden und Dependance Injection zu verwenden, um die richtige Fabrik für den Job zu übergeben. Ich bin mir noch nicht sicher, wie ich das machen soll. –

+0

Auch wenn Sie dieses Muster verwenden, müssen Sie die Fabrik trotzdem irgendwie implementieren. Selbst wenn du etwas vorgefertigt verwendest, wird dieses vorgefertigte Ding die Reflexion intern nutzen. –

+0

@CaseyCrookston, ich denke, es hängt von Ihrer Definition von Overkill ab. Ich habe mir nur das UML-Diagramm und den Beispielcode für die abstrakte Pattern-Factory-Implementierung angeschaut und es scheint, als würden Sie mit viel mehr Code enden, der (wahrscheinlich) schwerer zu pflegen sein wird. –

2
public class PositionFactory 
{ 
    private Dictionary<int, Type> _positions; 

    public PositionFactory() 
    { 
     _positions = new Dictionary<int, Type>(); 
    } 

    public void RegisterPosition<PositionType>(int id) where PositionType : Position 
    { 
     _positions.Add(id, typeof(PositionType)); 
    } 

    public Position Get(int id) 
    { 
     return (Position) Activator.CreateInstance(_positions[id]); 
    } 
} 

wie folgt verwendet:

  var factory = new PositionFactory(); 
      factory.RegisterPosition<Manager>(0); 
      factory.RegisterPosition<Clerk>(1); 

      Position p = factory.Get(0); //Returns a new Manager instance 
+0

Ok, danke dafür! Ich habe es eine Weile lang angeguckt, um sicherzugehen, dass ich alles verstehe, was vor sich geht. Frage: Wäre dies ein abstraktes Fabrikmuster? –

+1

Nein, dies ist nur ein einfaches Beispiel für ein normales Fabrikmuster. Es könnte jedoch als Teil einer abstrakten Fabrik verwendet werden, wenn Sie mehrere Implementierungen von 'PositionFactory' hätten. Eine abstrakte Fabrik ist nur eine Fabrik von Fabriken, also könntest du ein sehr ähnliches Muster verwenden, um eine abstrakte Fabrik zu machen. Tatsächlich könnten Sie beide Antworten so weit wie die 2 verschiedenen Implementierungen von 'PositionFactory' nehmen, da sie * fast * identische Schnittstellen verwenden und eine andere Factory haben, die eine 'PositionFactory' eines beliebigen Typs zurückgibt. –

+0

Das OP und dieses Beispiel funktionieren anders.Das OP gibt einen Code für jeden 'PositionType' vor, wobei dieses Beispiel jedem Code erlaubt, irgendeine Position –

1

Warum Dinge überkomplizieren? Hier ist eine einfache Lösung:

using System; 
using System.Collections.Generic; 

class Program 
{ 
    interface IPosition 
    { 
     string Title { get; } 
    } 

    class Manager : IPosition 
    { 
     public string Title 
     { 
      get { return "Manager"; } 
     } 
    } 

    class Clerk : IPosition 
    { 
     public string Title 
     { 
      get { return "Clerk"; } 
     } 
    } 

    class Programmer : IPosition 
    { 
     public string Title 
     { 
      get { return "Programmer"; } 
     } 
    } 

    class Factory 
    { 
     private List<IPosition> positions = new List<IPosition>(); 
     public Factory() 
     { 
      positions.Add(new Manager()); 
      positions.Add(new Clerk()); 
      positions.Add(new Programmer()); 
      positions.Add(new Programmer()); 
     } 

     public IPosition GetPositions(int id) 
     { 
      return positions[id]; 
     } 
    } 

    static void Main(string[] args) 
    { 
     Factory factory = new Factory(); 

     for (int i = 0; i <= 2; i++) 
     { 
      var position = factory.GetPositions(i); 
      Console.WriteLine("Where id = {0}, position = {1} ", i, position.Title); 
     } 
     Console.ReadLine(); 
    } 
} 

Hier ist, wie dies zu tun, ohne überhaupt mit Factory-Klasse:

using System; 
using System.Collections.Generic; 

class Program 
{ 
    interface IPosition 
    { 
     string Title { get; } 
    } 

    class Manager : IPosition 
    { 
     public string Title 
     { 
      get { return "Manager"; } 
     } 
    } 

    class Clerk : IPosition 
    { 
     public string Title 
     { 
      get { return "Clerk"; } 
     } 
    } 

    class Programmer : IPosition 
    { 
     public string Title 
     { 
      get { return "Programmer"; } 
     } 
    } 

    static void Main(string[] args) 
    { 
     List<IPosition> positions = new List<IPosition> { new Manager(), new Clerk(), new Programmer(), new Programmer() }; 

     for (int i = 0; i <= 2; i++) 
     { 
      var position = positions[i]; 
      Console.WriteLine("Where id = {0}, position = {1} ", i, position.Title); 
     } 
     Console.ReadLine(); 
    } 
} 
+0

Dies erfordert das Ändern der Factory-Klasse, wenn Sie eine neue hinzufügen Es ist auch immer nur in der Lage, eine einzige Instanz jeder Position zu verteilen. –

+0

Nun, dann müssen Sie nur Positionen außerhalb der Factory-Klasse hinzufügen. Sie müssen irgendwo hinzugefügt werden ... In diesem Fall müssen Sie Wenn ich eine Liste verwende, wird es – Tanuki

+1

tun Ich würde es nicht als Fabrik betrachten, es sei denn, es stellt tatsächlich das Ding dar, das es zurückgibt. Das ist mehr wie das 'Service Locator Pattern', nur ohne den" Service " –