2015-11-23 10 views
8

Sto cercando un semplice esempio di come implementare una classe factory, ma senza l'uso di uno Switch o di un'istruzione If-Then. Tutti gli esempi che riesco a trovare ne usano uno. Ad esempio, come si può modificare questo semplice esempio (sotto) in modo che la fabbrica effettiva non dipenda dallo Switch? Mi sembra che questo esempio viola il principio di apertura/chiusura. Mi piacerebbe poter aggiungere classi concrete ('Manager', 'Clerk', 'Programmer', ecc.) Senza dover modificare la classe factory.Modello di fabbrica senza interruttore o If/Then

Grazie!

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! Grazie a tutti! Ho imparato un sacco. Dopo aver ridestato tutto il feedback, ho mescolato alcune delle risposte e ne sono venuto fuori. Sarei aperto a ulteriori dialoghi su un modo migliore per farlo.

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

Un altro Edit:

E 'anche possibile creare un'istanza della interfaccia utilizzando un tipo sconosciuto:

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

che potrebbero poi essere chiamato come segue:

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

si poteva prendere uno sguardo alla [pattern Abstract Factory] (https: // en.wikipedia.org/wiki/Abstract_factory_pattern) e utilizzare l'iniezione di dipendenza per passare lungo la fabbrica giusta per il lavoro. – Adimeus

+0

Vorrei raccomandare qualcosa come Ninject o Autofac – tdbeckett

+0

Questo è un classico caso di iniezione di dipendenza. L'uso più basilare di qualsiasi contenitore IoC (Unity, Ninject, ecc.) Lo sta usando esattamente come una fabbrica glorificata. – Tanuki

risposta

4

ne dite di questo (nessun dizionario richiesto e si noti che si otterrà un errore di sintassi se il tentativo di Create<Position>()):

EDIT - Aggiornato per utilizzare un'interfaccia IPosition implementata in modo esplicito. Solo le istanze di IPosition possono accedere alle funzioni membro (ad esempio, <implementation of Manager>.Title non verrà compilato).

EDIT # 2 Factory.Create deve restituire un'Iposizione non T quando si utilizza l'interfaccia correttamente.

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

Mi piace molto la semplicità di questo. Ma quando lo provo, ottengo una console vuota. Fammi digerire questo un po '. –

+0

Non c'è alcun codice lì per produrre il contenuto delle posizioni. Aggiungi semplicemente un foreach (Posizione p nelle posizioni) ... –

+0

Come stai mappando i numeri di identificazione su _nove istanze? Vedo che è possibile estrarre un'istanza da 'posizioni' per indice, approssimativamente corrispondente agli id, ma cosa succede se hai bisogno di più istanze * separate * di un' Manager '? –

5

Si potrebbe fare uso di attributi personalizzati e reflec zione.

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

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

Nella fabbrica si potrebbe quindi ottenere tutte le classi che ereditano da Position e trovare quello che ha l'attributo PositionType con il valore corretto.

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, vedo che funziona. Ma la riflessione sembra essere eccessivo? Mi piace il suggerimento di cui sopra di Adimeus di utilizzare il modello astratto di fabbrica e utilizzare l'iniezione di dipendenza per passare lungo la fabbrica giusta per il lavoro. Non sono ancora sicuro di come farlo. –

+0

Anche usando quel modello, ANCORA devi implementare la fabbrica in qualche modo. Anche se usi qualcosa ti prema che quella cosa premade userà la riflessione internamente. –

+0

@CaseyCrookston, immagino dipenda dalla tua definizione di overkill. Ho appena visto il diagramma UML e il codice di esempio per l'implementazione di pattern factory astratta e sembra che si finirà con un codice molto più che (probabilmente) sarà più difficile da mantenere. –

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

Utilizzato in questo modo:

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

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

Ok, grazie per questo! L'ho guardato per un po 'per assicurarmi di capire tutto quello che sta succedendo. Domanda: questo si qualificherebbe come un modello astratto di fabbrica? –

+1

No, questo è solo un semplice esempio di un normale modello di fabbrica. Potrebbe però essere usato come parte di una fabbrica astratta, se aveste implementazioni multiple di 'PositionFactory'. Una fabbrica astratta è solo una fabbrica di fabbriche, quindi è possibile utilizzare un modello molto simile a questo per creare una fabbrica astratta. In effetti, si potrebbero prendere entrambe le risposte postate fino a 2 diverse implementazioni di 'PositionFactory', poiché usano * quasi * interfacce identiche e hanno un factory diverso che restituisce un' PositionFactory' di entrambi i tipi. –

+0

L'OP e questo esempio funzionano in modo diverso.L'OP predetermina un codice per ogni 'PositionType', dove questo esempio consente a qualsiasi codice di avere qualsiasi posizione –

1

Perché complicare eccessivamente le cose? Qui è una soluzione semplice:

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

Ecco come fare questo senza usare classe factory a tutti:

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

Ciò richiede la modifica della classe factory ogni volta che si aggiunge una nuova posizione, ed è anche solo in grado di distribuire una singola istanza di ciascuna posizione: –

+0

Bene, allora devi solo aggiungere posizioni al di fuori della classe Factory. Devono essere aggiunte da qualche parte ... In tal caso non si Ho anche bisogno di una classe di fabbrica, l'uso di una lista lo farebbe – Tanuki

+1

Non lo considererei una fabbrica a meno che non sia in * istante * la cosa che sta restituendo.Questo è più simile al 'Modello di localizzazione del servizio ', solo senza il" Servizio " –

Problemi correlati