C # Design Pattern per lavoratori con diversi parametri di input

10

Non sono sicuro di quale schema di progettazione possa aiutarmi a risolvere questo problema.

Ho una classe, "Coordinator", che determina quale classe Worker dovrebbe essere utilizzata - senza dover conoscere tutti i diversi tipi di Worker che ci sono - chiama semplicemente WorkerFactory e agisce sull'interfaccia comune di IWorker.

Quindi imposta il Worker appropriato affinché funzioni e restituisce il risultato del suo metodo 'DoWork'.

Questo è andato bene ... fino ad ora; abbiamo un nuovo requisito per una nuova classe Worker, "WorkerB" che richiede un'ulteriore quantità di informazioni, ovvero un parametro di input aggiuntivo, affinché faccia il suo lavoro.

È come se avessimo bisogno di un metodo DoWork sovraccarico con il parametro di input aggiuntivo ... ma poi tutti i Workers esistenti dovrebbero implementare quel metodo - che sembra sbagliato in quanto quei lavoratori non hanno davvero bisogno di quel metodo.

Come posso refactoring questo per mantenere il coordinatore ignaro di quale lavoratore viene utilizzato e ancora consentendo a ciascun lavoratore di ottenere le informazioni necessarie per svolgere il suo lavoro ma non ha nessun lavoratore fare cose di cui non ha bisogno?

Ci sono già molti lavoratori già esistenti.

Non voglio dover cambiare nessuno dei lavoratori concreti esistenti per soddisfare i requisiti della nuova classe WorkerB.

Ho pensato che forse un pattern Decorator sarebbe stato utile qui, ma non ho visto nessun decoratore decorare un oggetto con lo stesso metodo ma diversi parametri prima ...

Situazione nel codice:

public class Coordinator
{
    public string GetWorkerResult(string workerName, int a, List<int> b, string c)
    {
        var workerFactor = new WorkerFactory();
        var worker = workerFactor.GetWorker(workerName);

        if(worker!=null)
            return worker.DoWork(a, b);
        else
            return string.Empty;
    }
}

public class WorkerFactory
{
    public IWorker GetWorker(string workerName)
    {
        switch (workerName)
        {
            case "WorkerA":
                return new ConcreteWorkerA();
            case "WorkerB":
                return new ConcreteWorkerB();
            default:
                return null;
        }
    }
}

public interface IWorker
{
    string DoWork(int a, List<int> b);
}

public class ConcreteWorkerA : IWorker
{
    public string DoWork(int a, List<int> b)
    {
        // does the required work
        return "some A worker result";
    }
}

public class ConcreteWorkerB : IWorker
{
    public string DoWork(int a, List<int> b, string c)
    {
        // does some different work based on the value of 'c'
        return "some B worker result";
    }

    public string DoWork(int a, List<int> b)
    {
        // this method isn't really relevant to WorkerB as it is missing variable 'c'
        return "some B worker result";
    }    
}
    
posta JTech 28.03.2017 - 02:59
fonte

4 risposte

2

Ho riprogettato la soluzione sulla base del commento di @ Dunk:

...you already know all the parameters needed before the IWorker instance is created. Thus, you should have passed those arguments to the constructor and not the DoWork method. IOW, make use of your factory class. Hiding the details of constructing the instance is pretty much the main reason for the factory class's existence.

Quindi ho spostato tutti gli argomenti possibili necessari per creare un IWorker nel metodo IWorerFactory.GetWorker e ogni lavoratore ha già ciò di cui ha bisogno e il coordinatore può semplicemente chiamare worker.DoWork ();

    public interface IWorkerFactory
    {
        IWorker GetWorker(string workerName, int a, List<int> b, string c);
    }

    public class WorkerFactory : IWorkerFactory
    {
        public IWorker GetWorker(string workerName, int a, List<int> b, string c)
        {
            switch (workerName)
            {
                case "WorkerA":
                    return new ConcreteWorkerA(a, b);
                case "WorkerB":
                    return new ConcreteWorkerB(a, b, c);
                default:
                    return null;
            }
        }
    }

    public class Coordinator
    {
        private readonly IWorkerFactory _workerFactory;

        public Coordinator(IWorkerFactory workerFactory)
        {
            _workerFactory = workerFactory;
        }

        // Adding 'c' breaks Open/Closed principal for the Coordinator and WorkerFactory; but this has to happen somewhere...
        public string GetWorkerResult(string workerName, int a, List<int> b, string c)
        {
            var worker = _workerFactory.GetWorker(workerName, a, b, c);

            if (worker != null)
                return worker.DoWork();
            else
                return string.Empty;
        }
    }

    public interface IWorker
    {
        string DoWork();
    }

    public class ConcreteWorkerA : IWorker
    {
        private readonly int _a;
        private readonly List<int> _b;

        public ConcreteWorkerA(int a, List<int> b)
        {
            _a = a;
            _b = b;
        }

        public string DoWork()
        {
            // does the required work based on 'a' and 'b'
            return "some A worker result";
        }
    }

    public class ConcreteWorkerB : IWorker
    {
        private readonly int _a;
        private readonly List<int> _b;
        private readonly string _c;

        public ConcreteWorkerB(int a, List<int> b, string c)
        {
            _a = a;
            _b = b;
            _c = c;
        }

        public string DoWork()
        {
            // does some different work based on the value of 'a', 'b' and 'c'
            return "some B worker result";
        }
    }
    
risposta data 11.04.2017 - 00:23
fonte
4

Sarà necessario generalizzare gli argomenti in modo che si adattino a un singolo parametro con un'interfaccia di base e un numero variabile di campi o proprietà. Un po 'come questo:

public interface IArgs
{
    //Can be empty
}

public interface IWorker
{
    string DoWork(IArgs args);
}

public class ConcreteArgsA : IArgs
{
    public int a;
    public List<int> b;
}

public class ConcreteArgsB : IArgs
{
    public int a;
    public List<int> b;
    public string c;
}

public class ConcreteWorkerA : IWorker
{
    public string DoWork(IArgs args)
    {
        var ConcreteArgs = args as ConcreteArgsA;
        if (args == null) throw new ArgumentException();
        return "some A worker result";
    }
}

public class ConcreteWorkerB : IWorker
{
    public string DoWork(IArgs args)
    {
        var ConcreteArgs = args as ConcreteArgsB;
        if (args == null) throw new ArgumentException();
        return "some B worker result";
    }
} 

Nota i controlli null ... perché il tuo sistema è flessibile e in ritardo, non è sicuro anche per il tipo, quindi dovrai verificare il cast per assicurarti che gli argomenti passati siano validi.

Se davvero non vuoi creare oggetti concreti per ogni possibile combinazione di argomenti, puoi usare un tupla invece (non sarebbe la mia prima scelta.)

public string GetWorkerResult(string workerName, object args)
{
    var workerFactor = new WorkerFactory();
    var worker = workerFactor.GetWorker(workerName);

    if(worker!=null)
        return worker.DoWork(args);
    else
        return string.Empty;
}

//Sample call
var args = new Tuple<int, List<int>, string>(1234, 
                                             new List<int>(){1,2}, 
                                             "A string");    
GetWorkerResult("MyWorkerName", args);
    
risposta data 28.03.2017 - 04:03
fonte
1

Suggerirei una delle varie cose.

Se vuoi mantenere l'incapsulamento, in modo che i calliti non debbano sapere nulla sul funzionamento interno degli operai o della fabbrica dei lavoratori, allora avrai bisogno di cambiare l'interfaccia per avere il parametro extra. Il parametro può avere un valore predefinito, in modo che alcuni calliti possano ancora utilizzare solo 2 parametri. Ciò richiederà che tutte le librerie di consumo vengano ricompilate.

L'altra opzione la sconsiglio, poiché rompe l'incapsulamento ed è solo un OOP generalmente sbagliato. Ciò richiede anche che tu possa almeno modificare tutti i callites per ConcreteWorkerB . Puoi creare una classe che implementa l'interfaccia IWorker , ma ha anche un metodo DoWork con un parametro extra. Quindi, nei callites, prova a lanciare IWorker con var workerB = myIWorker as ConcreteWorkerB; e quindi utilizza il parametro three DoWork sul tipo concreto. Di nuovo, questa è una cattiva idea, ma è qualcosa che potresti fare.

    
risposta data 28.03.2017 - 03:23
fonte
0

@Jtech, hai considerato l'uso dell'argomento params ? Ciò consente di passare una quantità variabile di parametri.

link

    
risposta data 28.03.2017 - 16:25
fonte

Leggi altre domande sui tag