lundi 20 juillet 2020

Archetecture/Design-paterns for a app to process stream data, whose user-facing concepts include inheritance, variables, and aliases (late binding)

I’m wondering if there is a set of design patterns or general architecture that would best model this scenario, which has elements roughly resembling some OOP language features:

  • A Document contains a tree of configurations (Configs), and each Config contains a set of EventProcessors.

  • Each EventProcessor has various properties (a collection of EventProcessorProperties) whose types vary depending on the kind of EventProcessors.

  • The values of these EventProcessorProperties can be overridden by child Configs (like inheritance in OOP).

  • At any given point, one Config from the tree is “active” (the activeConfig).

  • The activeEventProcessorsSet is the set of EventProcessors formed by combining sets of EventProcessors from the activeConfig with the EventProcessors of each ancestor of the activeConfig.

  • Purpose: The state of the activeEventProcessorsSet is used to process a real-time-ish stream of MIDI data (a data stream which contains very few events per minute)


Other Stuff:

  • Each EventProcessor has a (possibly empty):
    • collection of EventProcessorProperty/values pairs which override the values EventProcessorProperties in ancestor Configs .
    • collection of named values (Variables), and the value of an EventProcessorProperty can be each have as its value an alias to a Variable (ie. when the value of a Variable changes, the value of the EventProcessorsProperty which refers to the Variable changes (like in a spreadsheet or a parametric CAD program, or Late Binding in certain languages). The Variable must be defined in the same Config as the EventProcessor, or an ancestor Config.
  • The value of a Variable:
    • is inherited by a Config from its ancestors, and can be overridden by child Configs.
    • can be an alias to to another Variable. This variable must be in the same Config or an ancestor Config.
  • The value of a EventProcessorsProperty:
    • has values which can be overridden by children Configs.
    • can be an object which itself has EventProcessorsProperties which can be overridable by a child Config, and each of those EventProcessorsProperty’s values can be an alias to a Variable.
  • The values of EventProcessorsProperties and Variables can be changed by the user during runtime.

I realize that the Observer pattern can be used to update the properties of a EventProcessors when:

  • one of its properties has been overridden in a child Config (or the overriding value is added, removed, or changes)
  • the value of an EventProcessorsProperty is an alias whose referent’s properties change (or the referent of the alias itself is reassigned). The Observer patterns can also be used to update a Variable whose value is an alias to another Variable when the value of the aliased Variable changes.

But is there another/better way than using the Observer pattern? That pattern is going to notify every EventProcessors/Variable who uses the recently overridden/modified value to update itself, even the EventProcessorsProperties which never end up being used used for processing the MIDI stream (there’s no way to know in advance what’s going to come down that data stream).

Is there an architecture or set of design patterns that would allow each use of an EventProcessorsProperty to have the appropriate values (given the inheritance chain and alias chains) when it is needed to process the midi stream? Is there a way which would would allow easy subclassing of EventProcessors and easy subclassing of the objects used as the values of EventProcessorProperties (and the objects used by them, etc - whose properties can all be overridden), with a clean API?

Basic structure in Java-ish pseudo-code:

class Document {
    Config rootConfig;
    Config activeConfig;

    // uses getActiveEventProcessors() and returns a new MidiMessage based on its internal and (possibly overridden) state.
    MidiMessage processMidiMsg(MidiMessage msg) {…} 

    private Set<EventProcessor> getActiveEventProcessors();
    void setActiveConfig(Config);
}

class Config {
    Set<Config> children;
    Config parent;

    // the value is of type Object, and will have to be cast to the appropriate type when needed
    Map<EventProcessorProperty, Object> overrides; 

    Set<EventProcessor> getProcessors {…};
    addChild(Config) {…] // calls set parent
    private setParent() {…}
}

class EventProcessor {
 
    // … a bunch of EventProcessorProperties… different types depending on subtype of events
}

Aucun commentaire:

Enregistrer un commentaire