Architettura software per due classi simili che richiedono parametri di input diversi per lo stesso metodo

3

Sto scrivendo codice Java per simulare una catena di approvvigionamento. La catena di approvvigionamento può essere simulata in entrambi una calza intermedia o una configurazione di cross-docking. Così, ho scritto due oggetti del simulatore IstockSimulator e XdockSimulator . Poiché i due oggetti condividono determinati comportamenti (ad esempio effettuando spedizioni, richiesta in arrivo), Ho scritto un oggetto simulatore astratto AbstractSimulator che è una classe genitore dei due oggetti del simulatore.

L'oggetto simulatore astratto ha un metodo runSimulation() che accetta un parametro di input della classe SimulationParameters . Fino ad ora, i parametri di simulazione contengono solo campi comuni a entrambi gli oggetti del simulatore, come randomSeed , simulationStartPeriod e simulationEndPeriod . Tuttavia, ora desidero includere campi specifici per il tipo di simulazione che viene eseguita, cioè una classe IstockSimulationParameters per una simulazione di calza intermedia, e una classe XdockSimulationParameters per una simulazione cross-docking.

La mia idea attuale è prendere il metodo runSimulation() fuori dalla classe AbstractSimulator , ma per mettere un metodo runSimulation(IstockSimulationParameters) nella classe IstockSimulator , e un metodo runSimulation(XdockSimulationParameters) nella classe IstockSimulator . Tuttavia, temo che questo approccio porti alla duplicazione del codice.

Che cosa dovrei fare?

A cura

Attualmente, la classe AbstractSimulator definisce il metodo runSimulation che chiama altri metodi astratti che sono definiti solo dalle classi figlio concrete.

public abstract class AbstractSimulator {
    public void runSimulation(SimulationParameters params) {
        int startPeriod = params.startPeriod;
        int endPeriod = params.endPeriod;
        for (int t = startPeriod; t < endPeriod; ++t) {
            submitReports(t);
            makeShipments(t);
            demandArrives(t);
        }
    }
    protected abstract void submitReports(int t);
    protected abstract void makeShipments(int t);
}

Una delle differenze tra IstockSimulator e XdockSimulator è quello in una configurazione di calza intermedia, le strutture di livello 3 presentano segnalazioni alle strutture di secondo livello, mentre in una configurazione di cross-dock, le strutture di livello 3 inviano segnalazioni alle strutture di primo livello. Quindi IstockSimulator e XdockSimulator hanno le loro implementazioni dei metodi submitReports e makeShipments .

Cosa ha funzionato per me

Grazie per le tue risposte. Ispirato dalle tue risposte e dopo averlo addormentato, Ho trovato la mia risposta basata sul refactoring. Vedi sotto.

    
posta I Like to Code 05.06.2014 - 23:26
fonte

3 risposte

0

Ispirato dalle tue risposte, e dopo averlo addormentato, Ho trovato una soluzione diversa ed elegante che comporta il refactoring del codice.

Definisco una classe SupplyChain che contiene informazioni sulla topologia della supply chain, i tempi di consegna delle spedizioni, ecc. Definisco due diverse classi di simulatore IstockSimulator e XdockSimulator . La politica di rifornimento e i parametri di simulazione (condizioni iniziali) sono dati al IstockSimulator al momento della sua creazione. Allo stesso modo per XdockSimulator . Quindi esiste un solo metodo runSimulation che accetta un parametro di input di SimulationParameters .

Ecco come appare nel codice Java.

SupplyChain supplyChain = // create new supply chain
SimulationParameters simulationParameters = // define simulation parameters
IstockSimulator isim = new IstockSimulator(supplyChain,
    istockPolicies, istockInitialConditions);
isim.runSimulation(simulationParameters);
XdockSimulator xsim = new XdockSimulator(supplyChain,
    xdockPolicies, xdockInitialConditions);
xsim.runSimulation(simulationParameters);
    
risposta data 13.06.2014 - 17:07
fonte
2

I am worried however, that this approach will lead to code duplication.

Sì, potrebbe.

What should I do?

Suggerisco di usare i farmaci generici. Mi piace così

public abstract class AbstractSimulation<T extends SimulationParameters> {
    public void runSimulation(T parameters) {
        // common functionality
    }
}
public class SimulationParameters {
   // common parameters
}
public class StockSimulationParameter extends SimulationParameters {
   // specific parameters
}
public class StockSimulation extends AbstractSimulation<StockSimulationParameter> {
    @Override
    public void runSimulation(StockSimulationParameter parameters) {
        super.runSimulation(parameters); 
    }
}

// the same for XDockSimluation and XDocSimulationParameters
(... omitted for brevity ...) 

Se lo fai, StockSimulation.runSimulation () accetterà solo istanze di StockSimulationParameters, ma non SimulationParameters:

StockSimulation stocksim = new StockSimulation();
// not OK
SimulationParamters simparamCommon = new SimulationParamters();
stocksim.runSimulation(simparamCommon);
// OK
StockSimulationParameter simparam = new StockSimulationParameter(); 
stocksim.runSimulation(simparam);
    
risposta data 06.06.2014 - 00:13
fonte
2

Il metodo runSimulation() deve essere definito nelle classi concrete perché utilizza parametri diversi, quindi ne consegue che si comporta diversamente. Se hai una funzionalità comune, inserisco il metodo runSimulation() nella classe concreta senza parametri. Qualsiasi funzionalità comune come i metodi utility / helper può essere inclusa nella classe astratta. Il costruttore, che è già specifico per l'implementazione, accetta i parametri.

Creare un nuovo oggetto per ogni simulazione, usando il costruttore per passare i parametri, ha due vantaggi. Innanzitutto, ignora il problema dell'interfaccia. Secondo, solidifica l'idea che una simulazione debba essere eseguita una volta sola (potrebbe avere uno stato interno per esempio, quindi potrebbe non essere comunque rientrante)

Se modifichi il disegno, questo diventa il modello di strategia :

public interface SimulationParameters { ... }

public interface Simulation<T extends SimulationParamters> {
  SimulationResult runSimulation();
}

public class StockSimulation implements Simulation<StockParameters> {
  public StockSimulation (StockParameters p) { ... }
}

public class DockSimulation implements Simulation<DockParameters> {
  public DockSimulation (DockParameters d) { ... }
}

Quindi lo usi in questo modo:

Simulation s1 = new StockSimulation(new StockParameters());
Simulation s2 = new DockSimulation(new DockParameters());

Potresti quindi passare a Executor o qualsiasi altra cosa per eseguirlo, possibilmente in parallelo. Questo è uno dei vantaggi di questo approccio, è molto flessibile.

Questo potrebbe evitare la duplicazione del codice, e sicuramente evita di inquinare l'interfaccia che credo sia la vera preoccupazione nel tuo ultimo paragrafo.

Duplicazione codice

Per evitare la duplicazione del codice nel metodo runSimulation() , prenderemo in considerazione l'approccio dell'utilizzo di composizione per incapsulare ogni < a href="https://en.wikipedia.org/wiki/Atomicity_%28database_systems%29"> atomico passaggio di una simulazione nel proprio oggetto figlio.

public interface SimulationComponent {
  void runStep(SimulationState state, ...);
}

Quindi, nella tua classe di simulazione, potresti avere componenti di simulazione:

public class StockSimulation implements Simulation<StockParameters> {

  private final List<SimulationComponent> components;

  public StockSimulation (StockParameters p) {
    List<SimulationComponent> c = new LinkedList<>();
    c.add(new SimulationComponentImpl1());
    c.add(new SimulationComponentImpl2());
    c.add(new SimulationComponentImpl3());
    components = Collections.unmodifiableList(c);
  }

  public SimulationResult runSimulation() {
    SimulationState state = ...;
    for (SimulationComponent c : components) {
      c.runStep(state);
    }
    return state.getResult();
  }
}

Senza informazioni dettagliate su come funziona la simulazione non posso dire con certezza esattamente come dovrebbe funzionare questo codice, ma credo che questo sia l'approccio generale corretto.

    
risposta data 05.06.2014 - 23:59
fonte

Leggi altre domande sui tag