3

I have an app with the following structure (simplified a bit here)...

var RecorderApp = function(canvas) {
  this.state = undefined;
  this.states = { record: recordState, save: saveState };
  this.init = function() {
    this.audEng = new AudioEngine();
    this.waveDisp = new WaveformDisplay(canvas);
    this.mouse = new MouseStatus(canvas);
    this.states.record.init(this);
    this.states.save.init(this);
    this.state = this.states.record;
    this.state.enter();
    this.state.execute();
  };

I need the audEng, waveDisp and mouse instances to talk to each other - waveDisp needs to pull waveform data from audEng, click events received by waveDisp need to trigger state changes in the parent class and so on.

My first thought was to pass 'this' as a parameter into each instances constructor and store the reference as this.parent, then I could "reach back up" as necessary and directly call the query methods and state change methods I wanted. In this case I think I can justify directly reaching up into the parent class to call a state change method e.g this.parent.save() but... reaching back up into the parent then down again into another class e.g. this.parent.audEng.getWaveform() makes me feel uneasy, am I right in thinking this violates the law of demeter? If this is an unwise/smelly route to go down is the right solution making sure all the inter class communications I need are done via methods defined in parent RecorderApp class? Or is the architecture I'm suggesting fundamentally dumb.

Edit because marked as duplicate: The other answer I have seen on here is quite C++ centric, opinion seems divided, gives no explicit example and best answer didn't answer my question in a way that was completely clear to me, in particular "other languages use an object" is a bit more vague a solution than I was hoping for.

1

IMO: Pass by reference is something I shudder at because it's way too convoluted for my liking.

I would strongly suggest cross object commutation be handled at the parent/ child level. Siblings should never talk directly.

In addition consider what would happen if you need to change reference or inheritance structure later? You'll have to go back and change all of those tree based references and at that point you'll likely have to relearn your dependencies as they're defined by reference.

Explicit methods don't only make your code base easier to read. They make it easier to use.

  • Okay, adding a load of explicit methods sounds more convoluted to me in the short term but I'm totally happy to do it if it's a better architectural decision, I'm trying to up my programming game here, not get this thing working quickly :) So would making all communication go via methods in the parent class count as the mediator pattern? – technicalbloke Sep 27 '16 at 10:14
  • @technicalbloke - While many suggestions given by the people in Snowman's possible duplicate question implied otherwise. I 100% believe this to be a RULE, not just a suggestion unless you enjoy writing buggy and unmaintainable code. "Child classes should not know who contains them" Thus, calling back into the parent requires the child to know who contains them. That means the interpretation you wrote in your comment is a big NO. Your design is bad, but if you must use what you have then you are better off passing what each class needs to who needs it. e.g. AudioEngine to WaveFormDisplay. – Dunk Sep 27 '16 at 19:52
  • @Dunk - Yes I thought it looked smelly, thanks for helping me clear it up. Happily I'm not stuck with this design, I'm rewriting an app I wrote quickly (and badly) a while back and trying to do a better job this time round. I'm very keen to reduce coupling in my code, but like most things worth doing it's easier said than done! – technicalbloke Sep 29 '16 at 8:29
3

Creating a new dependency within a class itself increases coupling, and makes code harder to maintain. Consider moving canvas out of the constructor, and passing in your dependencies (assuming you are planning to use them) as already instantiated with canvas.

Think about your objects in terms of composition, as opposed to strictly inheritance. Composition allows you to isolate parts of the code.

In your example, you are passing in canvas, which is used in turn by WaveformDisplay. If in the future you need to add something to WaveformDisplay, you'll have to pass it in to RecorderApp and then to WaveformDisplay. You avoid that by passing in a WaveformDisplay instance that's already initialized the way you need.

My assumption is that you don't really care if it's parent.audEng.getWaveForm() or parent.getWaveForm -- the important thing is getting the waveform data. In this case you could create a method within RecorderApp that accomplishes the same thing while reducing coupling:

var RecorderApp = function(audEng) { this.getWaveform = function() { audEng.getWaveform(); }; };

Also take a look at modules in JavaScript, either through the Module Pattern in vanilla JS or in some of the newer frameworks that have built-in features for managing dependencies.

  • The RecorderApp class implements a state pattern, the final state of which needs to reset the app to it's initial state, I don't see how I can do this if the other classes are instantiated outside this class and passed in :/ Also I don't see how instantiating them outside helps me make them talk to each other. Sorry if I'm being slow here, fairly new to JS and design patterns. – technicalbloke Sep 27 '16 at 10:08
  • Thanks for the updated answer, I agree adding methods to the parent is the way forward. Was thinking of going one step further and passing these into the other classes as callbacks to be called as needed, rather than having the children need to know about a method name in the parent class. – technicalbloke Sep 29 '16 at 8:44
  • Update: I did that. The child classes are now passed optional callbacks for each event which might cause a state change. I am much happier with that solution as the children don't have to know about the wider system state or any of the parent's methods/state. – technicalbloke Dec 13 '16 at 10:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.