3

I am writing Java code to simulate a supply chain. The supply chain can be simulated in either an intermediate stocking or a cross-docking configuration. So, I wrote two simulator objects IstockSimulator and XdockSimulator. Since the two objects share certain behaviors (e.g. making shipments, demand arriving), I wrote an abstract simulator object AbstractSimulator which is a parent class of the two simulator objects.

The abstract simulator object has a method runSimulation() which takes an input parameter of class SimulationParameters. Up till now, the simulation parameters only contains fields that are common to both simulator objects, such as randomSeed, simulationStartPeriod and simulationEndPeriod. However, I now want to include fields that are specific to the type of simulation that is being run, i.e. an IstockSimulationParameters class for an intermediate stocking simulation, and a XdockSimulationParameters class for a cross-docking simulation.

My current idea is take the method runSimulation() out of the AbstractSimulator class, but to put a runSimulation(IstockSimulationParameters) method in the IstockSimulator class, and a runSimulation(XdockSimulationParameters) method in the IstockSimulator class. I am worried however, that this approach will lead to code duplication.

What should I do?

Edited

Currently, the AbstractSimulator class defines the runSimulation method which calls other abstract methods that are only defined by the concrete child classes.

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

One of the differences between the IstockSimulator and the XdockSimulator is that in an intermediate stocking configuration, tier 3 facilities submit reports to tier 2 facilities, while in a cross-docking configuration, tier 3 facilities submit reports to tier 1 facilities. Thus the IstockSimulator and the XdockSimulator have their own implementations of the methods submitReports and makeShipments.

What worked for me

Thank you for your answers. Inspired by your answers, and after sleeping it over, I came up with my own answer based on refactoring. See below.

I Like to Code
  • 223
  • 1
  • 3
  • 10

3 Answers3

2

The runSimulation() method has to be defined in the concrete classes because it uses different parameters, so it follows that it behaves differently. If you have common functionality, I would put the runSimulation() method in the concrete class with no parameters. Any common functionality like utility/helper methods can go in the abstract class. The constructor, which is already implementation-specific, accepts the parameters.

Creating a new object for each simulation, using the constructor to pass in parameters, has two benefits. First, it sidesteps the interface issue. Second, it solidifies the idea that a simulation should only be run once (it may have internal state for example, so it might not be reentrant anyway).

If you change the design this becomes the strategy pattern:

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) { ... }
}

Then you use it like this:

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

You could then pass this off to an Executor or whatever else to run it, possibly in parallel. That is one of the advantages of this approach, it is very flexible.

This may avoid code duplication, and it definitely avoids polluting the interface which is I believe the real concern in your last paragraph.

Code Duplication

To avoid code duplication in the runSimulation() method, I would consider the approach of using composition to encapsulate each atomic step of a simulation in its own child object.

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

Then, in your simulation class, you could have simulation components:

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

Without detailed information about how the simulation works I cannot say for sure exactly how this code should work, but I believe this is the correct general approach.

  • ok you've put one angle of the question in code, but how would you reduce code duplication? – miraculixx Jun 05 '14 at 22:06
  • Break your simulation behaviour into modular classes. Share these between the two simulations and their base as appropriate. Composition > Inheritance. – Mr Cochese Jun 05 '14 at 23:14
  • Without more information about the internals I cannot say for sure if code duplication is a concern. However, if the parameters are different for each simulation, that tells me that the execution has to be different as well. How much different, I do not know: but using other objects to compose behavior is the correct approach as @MrCochese says. You may even be able to use a [decorator pattern](https://en.wikipedia.org/wiki/Decorator_pattern) to compose an object chain that makes the simulation "go." I will edit my answer to address this. –  Jun 06 '14 at 12:37
2

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

yes, it might.

What should I do?

I suggest using generics. Like so

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 ...) 

If you do this, StockSimulation.runSimulation() will only accept StockSimulationParameters instances, but not SimulationParameters:

StockSimulation stocksim = new StockSimulation();
// not OK
SimulationParamters simparamCommon = new SimulationParamters();
stocksim.runSimulation(simparamCommon);
// OK
StockSimulationParameter simparam = new StockSimulationParameter(); 
stocksim.runSimulation(simparam);
miraculixx
  • 3,103
  • 13
  • 21
0

Inspired by your answers, and after sleeping it over, I came up with a different and elegant solution which involves refactoring the code.

I define a class SupplyChain which contains information about the supply chain topology, the lead times for shipments, etc. I define two different simulator classes IstockSimulator and XdockSimulator. The replenishment policy and the simulation parameters (initial conditions) are given to the IstockSimulator at the time of its creation. Similarly for the XdockSimulator. Thus there is only one runSimulation method that takes in an input parameter of SimulationParameters.

This is what it looks like in Java code.

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);
I Like to Code
  • 223
  • 1
  • 3
  • 10