2015-04-27 18 views
7

Ho un oggetto factory ChallengeManager per generare istanze di un oggetto Challenge per un gioco che sto costruendo. Ci sono molte sfide I costruttori per ciascuna derivazione di classe Challenge sono diversi, tuttavia esiste un'interfaccia comune tra di loro, definita nella classe base.Modello di fabbrica per la creazione di molte classi derivate

Quando chiamo manager.CreateChallenge(), restituisce un'istanza di Challenge, che è uno dei tipi derivati.

Idealmente, mi piacerebbe mantenere il codice per la costruzione dell'oggetto all'interno della classe derivata stessa, in modo che tutto il codice relativo a quell'oggetto sia co-localizzato. Esempio:

class Challenge {} 

class ChallengeA : Challenge { 
    public static Challenge MakeChallenge() { 
    return new ChallengeA(); 
    } 
} 

class ChallengeB : Challenge { 
    public static Challenge MakeChallenge() { 
    return new ChallengeB(); 
    } 
} 

Ora, il mio ChallengeManager.CreateChallenge() chiamata ha solo bisogno di decidere la classe di chiamare MakeChallenge() su. L'implementazione della costruzione è contenuta dalla classe stessa.

Utilizzando questo paradigma, ogni classe derivata deve definire un metodo statico MakeChallenge(). Tuttavia, poiché il metodo è statico, non sono in grado di utilizzare un'interfaccia qui, richiedendola.

Non è un grosso problema, dal momento che posso facilmente ricordare di aggiungere la firma del metodo corretto a ciascuna classe derivata. Tuttavia, mi chiedo se ci sia un design più elegante che dovrei prendere in considerazione.

+0

Fortemente attaccato al disegno che stai suggerendo: come identificheresti quale Classe istanziare in 'ChallengeManager.CreateChallenge()'? e; se conosci già la classe da istanziare, perché non puoi semplicemente istanziarla nel metodo 'CreateChallange'? – Abhinav

+0

In questo caso, ciò che accadrà è un metodo CreateChallenge() molto lungo e non così gestibile come la scomposizione della costruzione nelle classi pertinenti. – gdbj

+0

Il tuo design è il migliore in termini di design. Preferirei non mettere più metodi di fabbrica statici nelle classi concrete, però. Utilizzare i costruttori effettivi per tutto ciò che è la responsabilità propria delle classi e il processo decisionale e la costruzione delle dipendenze in 'CreateChallenge'. Se 'CreateChallenge' diventa troppo lungo per i tuoi gusti, estrai i metodi da esso per rendere più chiara l'intenzione. Se è davvero lungo, estrailo nella sua classe. YMMV – bstenzel

risposta

22

Mi piace molto il modello che si sta descrivendo e lo usano spesso. Il modo in cui mi piace farlo è:

abstract class Challenge 
{ 
    private Challenge() {} 
    private class ChallengeA : Challenge 
    { 
    public ChallengeA() { ... } 
    } 
    private class ChallengeB : Challenge 
    { 
    public ChallengeB() { ... } 
    } 
    public static Challenge MakeA() 
    { 
    return new ChallengeA(); 
    } 
    public static Challenge MakeB() 
    { 
    return new ChallengeB(); 
    } 
} 

Questo modello ha molte belle proprietà. Nessuno può creare un nuovo Challenge perché è astratto. Nessuno può creare una classe derivata perché il ctor predefinito di Challenge è privato. Nessuno può arrivare a ChallengeA o ChallengeB perché sono privati. Si definisce l'interfaccia su Challenge e questa è l'unica interfaccia che il client deve capire.

Quando il cliente vuole un A, chiede uno Challenge per uno, e lo ottengono. Non è necessario preoccuparsi del fatto che dietro le quinte, A è implementato da ChallengeA. Hanno solo un Challenge che possono usare.

+1

Mi piace l'idea di implementare la fabbrica nella classe base astratta stessa. –

+0

Ci sono un paio di aspetti negativi di questo approccio, però. Poiché 'ChallengeA' e' ChallengeB' sono private, non puoi testarli. Dal momento che non stai usando 'fabbrica astratta', non puoi prendere in giro la fabbrica nei test unitari. Più metodi di fabbrica per lo stesso tipo significa che stai decidendo quale classe creare nel codice chiamante. La bellezza di una fabbrica per quanto mi riguarda è che stai decidendo su quale implementazione concreta creare in un unico punto. Questo è fondamentalmente un codice legacy in base alla progettazione. – bstenzel

+1

@bstenzel ChallengeA e ChallengeB sono pensati per essere utilizzati dall'interfaccia Challenge. Quindi se ottieni oggetto di ChallengeA chiamando Challenge.MakeA(), lo stesso per ChallengeB, puoi testare entrambi. – CreativeMind

2

Si sta "decentralizzando" la fabbrica, in modo tale che ciascuna sottoclasse sia responsabile della creazione stessa.

Più comunemente si avrebbe una fabbrica centrale che sarebbe a conoscenza dei possibili sottotipi e come costruirli (spesso abbastanza, semplicemente creando una nuova istanza e restituendo quell'istanza digitata come un'interfaccia comune o una classe di base comune). Questo approccio evita il problema che hai attualmente. Non vedo alcun beneficio per il tuo approccio attuale. Attualmente non si ottiene incapsulamento o riutilizzo del codice rispetto all'implementazione più tipica di una fabbrica.

Per ulteriore riferimento, uno sguardo ai

http://www.oodesign.com/factory-pattern.html

+0

ya, è una variante dello scenario classico, principalmente per contenere i dettagli di costruzione della classe derivata per la leggibilità. – gdbj

1

Non necessariamente la risposta che si sta cercando, ma ... È possibile utilizzare l'implementazione successiva, se è possibile spostarsi dal metodo statico per classe.

using System; 

public class Test 
{ 
    public static void Main() 
    { 
     var c1 = ChallengeManager.CreateChallenge(); 
     var c2 = ChallengeManager.CreateChallenge(); 
     //var c = ChallengeManager.CreateChallenge<Challenage>(); // This statement won't compile 
    } 
} 

public class ChallengeManager 
{ 
    public static Challenage CreateChallenge() 
    { 
     // identify which challenge to instantiate. e.g. Challenage1 
     var c = CreateChallenge<Challenage1>(); 
     return c; 
    } 

    private static Challenage CreateChallenge<T>() where T: Challenage, new() 
    { 
     return new T(); 
    } 
} 

public abstract class Challenage{} 
public class Challenage1: Challenage{} 
public class Challenage2: Challenage{} 
+1

Un buon approccio per usare un metodo parametrizzato. –

+2

Con questo approccio, si sta violando il principio di inversione delle dipendenze rendendo il codice chiamante dipendente dalle implementazioni concrete. Non ottieni nulla su 'new Challenge1()' quando chiami 'CreateChallenge ()'. L'intero punto di utilizzo di una fabbrica è di rendere le classi ** solo ** dipendenti dalle astrazioni. – bstenzel

+0

Concordo, questo non ha realmente implementato il modello di fabbrica. La mia risposta (aggiornata) era in gran parte incentrata su questa affermazione in questione: _Ora, il mio ChallengeManager.La chiamata CreateChallenge() ha solo bisogno di decidere su quale classe chiamare MakeChallenge(). L'implementazione della costruzione è contenuta dalla classe stessa. In questo modo elimina la necessità del metodo 'MakeChallenge'. – Abhinav