C #: quando si dovrebbe seguire lo schema del metodo di fabbrica anziché il modello di fabbrica

2

Ho pubblicato questa domanda su Stack Overflow e alcune persone hanno suggerito di postarla qui.

Capisco sia il modello di metodo factory che factory. Nello schema factory creiamo dinamicamente una mia classe classificata da un'altra funzione di classe, dove passo qualche parametro a un'altra funzione di classe e in base a quel parametro un'altra funzione di classe restituisce l'istanza di classe giusta.

Nel modello di metodo di fabbrica dobbiamo procedere con un ulteriore passaggio. Nella sottoclasse del modello metodo factory creare l'istanza della mia classe. Non trovo uno scenario in cui le persone debbano seguire un modello di metodo di fabbrica. Quindi, per favore qualcuno viene con uno scenario in cui il normale modello di fabbrica non verrà utilizzato, piuttosto che le persone preferiscono utilizzare il metodo del metodo di fabbrica.

Qui sto postando due serie di codice prima una per modello di fabbrica e una seconda per modello di metodo di fabbrica

Primo set di codice in cui è stato utilizzato lo schema di fabbrica:

public enum Shipper
{
    UPS = 1,
    FedEx = 2,
    Purolator = 3
}

public interface IShip
{
    void Ship();
}

public class ShipperPurolator : IShip
{
    public void Ship()
    {
        //-- code logic to implement shipping method for Purolator
        MessageBox.Show("Purolator ship start");
    }
}

public class ShipperUPS : IShip
{
    public void Ship()
    {
        //-- code logic to implement shipping method for Purolator
        MessageBox.Show("UPS ship start");
    }
}

public class ShipperFexEx : IShip
{
    public void Ship()
    {
        //-- code logic to implement shipping method for Purolator
        MessageBox.Show("FedEx ship start");
    }
}

public class ShipperFactory
{
    public static IShip CreateInstance(Shipper enumModuleName)
    {
        IShip objActivity = null;

        switch (enumModuleName)
        {
            case Shipper.UPS:
                objActivity = new ShipperUPS();
                break;
            case Shipper.FedEx:
                objActivity = new ShipperFexEx();
                break;
            case Shipper.Purolator:
                objActivity = new ShipperPurolator();
                break;
            default:
                break;
        }
        return objActivity;
    }
}

Chiamando in questo modo:

IShip objActivity = null;

private void btnUPS_Click(object sender, EventArgs e)
{
    objActivity = ShipperFactory.CreateInstance(Shipper.UPS);
    objActivity.Ship();
}

private void btnFedEx_Click(object sender, EventArgs e)
{
    objActivity = ShipperFactory.CreateInstance(Shipper.FedEx);
    objActivity.Ship();
}

private void btnPurolator_Click(object sender, EventArgs e)
{
    objActivity = ShipperFactory.CreateInstance(Shipper.Purolator);
    objActivity.Ship();
}

Ora la stessa cosa è stata eseguita con lo schema dei metodi di produzione in cui devo scrivere altro codice per completare il lavoro

public interface IShip
{
    void Ship();
}

public class ShipperPurolator : IShip
{
    public void Ship()
    {
        //-- code logic to implement shipping method for Purolator
        MessageBox.Show("Purolator ship start");
    }
}

public class ShipperUPS : IShip
{
    public void Ship()
    {
        //-- code logic to implement shipping method for Purolator
        MessageBox.Show("UPS ship start");
    }
}

public class ShipperFedEx : IShip
{
    public void Ship()
    {
        //-- code logic to implement shipping method for Purolator
        MessageBox.Show("FedEx ship start");
    }
}

Inizio della classe di fabbrica:

public interface IShipFactory
{
    IShip GetShipper();  
}

public class ShipperFexExFactory : IShipFactory
{
    public IShip GetShipper()
    {
        //-- code logic to implement shipping method for Purolator
        //MessageBox.Show("FedEx ship start");
        return new ShipperFedEx();
    }
}

public class ShipperUPSFactory : IShipFactory
{
    public IShip GetShipper()
    {
        //-- code logic to implement shipping method for Purolator
        return new ShipperUPS();
    }
}

public class ShipperPurolatorFactory : IShipFactory
{
    public IShip GetShipper()
    {
        //-- code logic to implement shipping method for Purolator
        return new ShipperPurolator();
    }
}

Chiamando in questo modo:

IShipFactory _IShipFactory = null;

private void btnUPS_Click(object sender, EventArgs e)
{
    _IShipFactory = new ShipperUPSFactory();
    _IShipFactory.GetShipper().Ship();
}

private void btnFedEx_Click(object sender, EventArgs e)
{
    _IShipFactory = new ShipperFexExFactory();
    _IShipFactory.GetShipper().Ship();
}

private void btnPurolator_Click(object sender, EventArgs e)
{
    _IShipFactory = new ShipperPurolatorFactory();
    _IShipFactory.GetShipper().Ship();
}

Qual è il metodo preferito per scegliere il modello di metodo di fabbrica?

    
posta Mou 06.04.2017 - 16:53
fonte

2 risposte

3

In effetti, c'è troppa carta da parati in corso.

Questo avrebbe senso solo se dovessi attenersi a C # 1.0.

Quindi, supponendo che tu stia implementando in C # 3.0+, un possibile punto di partenza potrebbe essere,

(mantenendo invariate le convenzioni di structuring + naming, per motivi di linearità)

public enum Shipper
{
    UPS,
    FedEx,
    Purolator
}

public interface IShip
{
    //Etc
}

public abstract class ShipperBase : IShip
{
    //Etc
}

public class ShipperUPS : ShipperBase
{
    //Etc
}

public class ShipperFedEx : ShipperBase
{
    //Etc
}

public class ShipperPurolator : ShipperBase
{
    //Etc
}

public class ShipperFactory
{
    public static readonly IDictionary<Shipper, Func<IShip>> Creators =
        new Dictionary<Shipper, Func<IShip>>()
        {
            { Shipper.UPS, () => new ShipperUPS() },
            { Shipper.FedEx, () => new ShipperFedEx() },
            { Shipper.Purolator, () => new ShipperPurolator() }
        };

    public static IShip CreateInstance(Shipper enumModuleName)
    {
        return Creators[enumModuleName]();
    }
}

'Spero che questo aiuti.

    
risposta data 06.04.2017 - 20:43
fonte
2

In entrambe le situazioni, lo schema di fabbrica viene implementato male. Non utilizzerei nessuno schema come codificato qui nel codice di produzione.

In teoria, l'unica differenza pratica tra il modello Factory e il metodo Factory è ciò che l'"oggetto dipendente" che ha bisogno di un modo per creare le cose deve farlo. Nel modello Factory, l'oggetto dipendente ha un'istanza della classe factory. Nel modello Metodo di fabbrica, l'oggetto dipendente ha un riferimento direttamente a un metodo che può chiamare, sotto forma di delegato.

Il primo esempio deve essere evitato perché utilizza un'istruzione switch . L'interruttore esiste per buone ragioni, tuttavia il suo uso nel moderno C # è un odore di codice, perché ci sono opzioni migliori per molti dei suoi casi d'uso, incluso questo. In questa situazione, l'istruzione switch deve essere modificata ogni volta che viene aggiunta una nuova implementazione IShip alla base di codice. Quindi, quando aggiungi DHL o USPS come opzione di spedizione, non solo hai bisogno di una nuova IShip, devi modificare la fabbrica. Questo è generalmente da evitare.

Nel secondo schema, hai essenzialmente una classe factory per ogni implementazione IShip. Ora, quando crei una nuova implementazione IShip (), hai bisogno di una nuova implementazione IShipFactory. Questo è ancora più semplice e più cambiamenti necessari per il codice base. Inoltre, ora stai strettamente aderendo non al costruttore dell'implementazione IShip (che è ciò che gli schemi Factory evitano), ma al costruttore di IShipFactory; non stai molto meglio, potresti anche solo rianimare IShip invece che Factory.

Ciò di cui hai bisogno sono alcuni schemi aggiuntivi, vale a dire il modello di strategia e il modello di iniezione di dipendenza.

Il modello di strategia è un termine generico per qualsiasi oggetto che incapsula un set di regole per la scelta della migliore implementazione di una dipendenza necessaria. In questo caso, utilizzerà il valore di enumerazione fornito. Il metodo ShipperFactory.CreateInstance () è un esempio di un modello di strategia, ma esiste un'implementazione migliore per il tuo caso d'uso; il dizionario dei delegati:

public interface IShip
{
    Shippers Shipper { get; }

    void Ship();
}

public enum Shippers
{
    UPS,
    FedEx,
    Purolator,
}

public class UPSShipper : IShip
{
    public Shippers Shipper => Shippers.UPS;
    public void Ship() {  /*TODO: Implement*/ }
}

public class FedExShipper : IShip
{
    public Shippers Shipper => Shippers.FedEx;
    public void Ship() {  /*TODO: Implement*/ }
}

//Add additional implementations

public class ShipperFactory
{
    public Dictionary<Shippers, Func<IShip>> shipperCreators { get; private set; }
    public ShipperFactory()
    {
        //Here, we use reflection and Linq to find all IShip implementations;
        //other methods to dynamically set up the dictionary exist
        shipperCreators = Assembly.GetExecutingAssembly().GetTypes()
            .Where(t => typeof (IShip).IsAssignableFrom(t) && t.IsInterface == false)
            .Select(t => new Func<IShip>(() => Activator.CreateInstance(t) as IShip))
            .ToDictionary(f => f().Shipper);
    }

    public IShip CreateInstance(Shippers type)
    {
        return shipperCreators[type]();
    }

    public Func<IShip> GetFactoryMethod(Shippers type)
    {
        return shipperCreators[type];
    }
}

Il primo cambiamento riguarda l'implementazione IShip stessa; non solo identifica il tipo di mittente utilizzato dal proprio nome oggetto, ma espone anche una proprietà (un "discriminatore") che identifica l'implementazione anche dietro l'astrazione di un'IShip. Ciò consente a ciascuna implementazione di essere autoidentificante, che usiamo quando si imposta la fabbrica.

Lo stesso stabilimento utilizza la riflessione, identificando i tipi contenuti nello stesso assieme della classe factory stessa che implementa IShip e che non sono la fabbrica. Quindi crea una funzione per ogni tipo che costruisce un'istanza di quel tipo e la esegue una volta solo per generare il valore enum da utilizzare come chiave per il dizionario. come ho menzionato nei commenti, non è necessario utilizzare la riflessione; è possibile utilizzare un file di configurazione o disporre di un altro oggetto che ha ancora più conoscenza nelle chiavi e delegare la funzione alla fabbrica. La riflessione è semplicemente un modo accessibile e autonomo per una fabbrica di trovare ciò di cui ha bisogno senza che lo sviluppatore glielo chieda (il che significa che lo sviluppatore deve comunicare alla fabbrica ogni nuova IShip che crea).

Ora, questa classe può essere utilizzata per un modello di fabbrica o un metodo di metodo di fabbrica, in base al metodo che si desidera utilizzare. Per il modello di fabbrica, chiama semplicemente CreateInstance() e passa l'enumerazione. Per utilizzarlo come generatore del metodo Factory, chiama invece GetFactoryMethod() .

Ora, per rispondere alla vera domanda, qual è la differenza? La differenza è se l'oggetto chiamante ha o ha bisogno di conoscenze specifiche sul tipo di mittente che dovrebbe essere richiesto. Se un metodo richiede l'accesso a diverse implementazioni IShip, dovrebbe utilizzare la fabbrica. Se, tuttavia, un metodo o un oggetto deve solo sapere che ha bisogno di un IShip, è possibile iniettare il risultato di GetFactoryMethod come parametro del metodo o del costruttore dell'oggetto, e quindi ogni volta che ha bisogno di un IShip, chiama il metodo. Non è richiesta alcuna conoscenza specifica riguardo a dove provenga l'IShip, o anche il metodo factory.

    
risposta data 12.04.2017 - 22:06
fonte

Leggi altre domande sui tag