简体   繁体   中英

Setting up a generic architecture for a block based model design (Java)?

little time ago I started to get excited about OOP and Java. As I am not an IT guy but rather an engineer and work a lot with Matlab/Simulink, I decided a little simple modelling and simulation system in Java to be my first project. I am trying to create a model consisting of blocks. Each block can have inputs and outputs signals and blocks can be connected. Sets of blocks can create subsystems, which is basically a grouping and reusing of a sub model. A complete model, which I call system, can be simulated in discrete time steps.

Block input1 |¯¯¯¯¯¯| ---->|custom| output1 input2 | code |----> ---->| | |______|

First I created a class Signal<T> with some fields like name and units. The data of a signal is stored in a field List data, so in a constructor I call

data = new ArrayList<T>(width);

This way one signal can contain a list of values. Now I created a class AbstractGeneralBlock

public abstract class AbstractGeneralBlock implements SystemBlock

This class implements some fields and methods common for all kinds of block. SystemBlock defines methods each block must have, like: getOutputs, getInputs, initializeBlock, etc. But each block object should also have an addInput and setInputs method. This is where it gets tricky.

A custom block can have more than one input signal and each can be of different type. For the example above, input1 could be of type Signal<Double> and input2 might be of Signal<Boolean> . Because all inputs in a block are stored in a List<Signal<T>> , I defined the method addInput in my SystemBlock interface as following:

public void addInput(Signal<?> input);

Until now everything works, but as soon as I want to create a specific block like Gain (basically output=input*factor) I get problems. The Gain class is supposed to be parametrized:

public class Gain<T> extends AbstractGeneralBlock

As soon as I want to implement method addInput, I want it to accept only inputs of type Signal, but my addInput needs an argument of type Signal<?> . So I can not control what I store in the List of inputs in the Gain class.

On the other hand, if I define the addInput method in SystemBlock interface with T instead of ?, I can not have multiple inputs with different Signal types for one block.

I am not sure if my problem is clear and I try do my best describing it. In short, I need a general architecture for blocks and signals. Blocks need to be able to hold inputs of different Signal types but some blocks must be parametrized, so the user can control the input type. Unfortunately I can not check the input argument type during runtime with instanceOf T because of erasures.

Is this problem completely ridiculous, or is there some nice design pattern which could help me? Thanks in advance.

If you need to be able to restrict what type addInput can accept for different subclasses, I'm not sure how to do that except give it a type parameter, which in this case would mean giving SystemBlock a type parameter. Note that for some subclasses you could use Object if you have blocks that can accept multiple unrelated types.

Below is something like Item 29 from Effective Java, 2nd Edition: Consider typesafe heterogeneous containers :

class Input<T> {
    final T val;

    Input(T val) {
        this.val = val;
    }


    T getVal() { return val; }

    @Override
    public String toString() {
        return "Input{" +
                "val=" + val +
                '}';
    }
}

class Block<X> {
    private final Map<Class<?>, Object> inputs = new HashMap<>();

    <T extends X> void addInput(Class<T> inputClass, Input<T> input) {
        inputs.put(inputClass, input);
    }

    <T extends X> Input<T> getInput(Class<T> inputClass) {
        @SuppressWarnings("unchecked")
        Input<T> input = (Input<T>) inputs.get(inputClass);
        return input;
    }

    @Override
    public String toString() {
        return "Block{" +
                "inputs=" + inputs +
                '}';
    }
}    

class Temp {
    public static void main(String[] args) {
        Block<Number> block = new Block<>();
        block.addInput(Long.class, new Input<>(1L));
        block.addInput(Integer.class, new Input<>(1));

        System.out.println(block);
        System.out.println(block.getInput(Integer.class));
    }
}

Unfortunately, I don't know how you can make Block (or code using particular Block instance) easily aware of relations between various <T> of blocks' Input<T> in more precise manner than above code shows, since generics are compile-time only. The only way I can think of is to maintain list of Class<T> passed inside a block, then you can validate that list during run-time.

While this far from elegant, I believe, you can't get more out of Java type system. Not sure, if it helps you, though.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM