0

[Normally I post on StackOverflow but as this is more a design/theory question rather than a code question I'll give it a shot here]

Most of my applications currently use a core object model that I originally wrote 6 years ago and which has just grown as needed - as with most of my stuff, it was coded as is, without thinking up a design first. Part of it is too inflexible, part is poorly designed and yet more is clunky. So I decided to start again from scratch rather than trying to retrofit a new API on top of the existing one. And I also decided to actually map out the full API first upfront, then write the code for it.

So far that approach is paying off I suppose and I have a nice little model brewing. However, I have one quandary in the new design. If you consider the automation models offered by things like Microsoft Word, most primary objects have a property named Application which points back to a core object. My existing object model follows pretty much the same principle and I am using that in the new one too.

The current API however has a mix of approaches. Some objects store a reference to that root object. Others don't, and the property simply looks at a singleton "service" object, which at it's heart has a ServiceContainer to look up registered objects. Yes, a service locator. Some get it passed in through a constructor, others look it up then store a reference. I normally like consistency, but this is a mess.

My new design isn't changing this approach - objects still will have a property back the the root object with the idea being all the objects behave the same and I don't have to (manually) go hunting for an application when writing code for a given implementation.

With that background information out of the way, here's my question. Should each object hold a reference to the root object, or should it not store a direct reference, but look it up somehow, ie from a singleton or a service locator.

With the former approach, I'm going to use more memory - 4 bytes extra per object per object as I currently compiled everything as 32bit. While that doesn't sound a lot and I'm sure I'm not going to be creating many thousands of objects I sort of want the base API to be as efficient as possible as no doubt the apps I stick on top of it won't be! This approach also means I'm going to have to pass that object reference around in every single constructor, not much of a problem for real code but makes writing tests a bit more complicated.

The latter approach means I save memory, but then I continue to use the bad practice of a service locator, and I supposed there's a at least the hint of a performance issue as having to lookup a reference isn't quite going to be as quick as returning a direct one.

Or, is there another approach that large object models use that I haven't considered above? As mentioned I normally just dive in, make something that works then leave it alone until it breaks or I need it to do something else.

I'm guessing the answer is going to be "just store the reference and stop complaining" but better to get a feel for other peoples opinions!

2
  • Without having any information about how your object model looks like, and what it actually "models", it is very hard to give you any good advice here. But why do you need a core object at all? What does it represent? The .NET framework, for example, has no "Application" object either.
    – Doc Brown
    Jan 4, 2014 at 17:54
  • The object model is essentially an application model, so you have the core application, then user interface elements, opened documents, plugins, things like that. Quite a lot of things need to talk to something else (ie ui elements disabling themselves, plugins finding out when things change, that sort of example) so they all go to the application to find out what's what. That's why I used Word as an example, it's the same sort of model - except mine are nowhere near as complex. Jan 4, 2014 at 17:59

4 Answers 4

1

Core objects have a strong tendency to become "God objects" over time, so the first thing I would ask here is "do I really need that thing?". The object models of Word or Excel reflect the fashion in object modeling ~15 year ago, when those models where created. But look for example at the .NET framework and, for example, WinForms - there is no Application object, only a static class "Application" with a handful of methods, and in real programs you very seldom need to access that class.

But if you are convinced you really need some kind of "Application" or "core" object, I would tell you "just use the service locator consistently and stop complaining about performance issues you don't really have". The service locator is sometimes seen as an anti pattern because it could introduce hidden global dependencies, but if you introduce your global dependency to a "core object" through a service locator or by a reference in every of your main objects does not really make a difference, I would consider that as "equally bad".

I suggest you read the "Disadvantages" part of the Wikipedia article about Service Locator, and think a while which of those points really apply in your situation, when accessing your "core object" through such a mechanism, and which of those points are caused just by the fact you have such a "core object".

1
  • I hadn't heard of "god" objects before, so there's one new thing. With that said though, I don't see why it's so bad to have a carefully crafted root that binds things together. The automation models in Word are pretty old - but even when the newest Visual Studio is similar (with the DTE) is that not suggestive? If you have a "Paste" command, what's really so wrong with asking the application what the active document is and then doing something with said document? I do not disagree with the article on service locators hence finding out information on other approaches I haven't considered. Jan 5, 2014 at 4:37
0

Should each object hold a reference to the root object, or should it not store a direct reference, but look it up somehow, ie from a singleton or a service locator.

In an ideal world, none of your children should have any knowledge of the thing that contains them. Specifically by doing so, you've eliminated any hope of reuse in a different application. Worse yet, these root objects often let you get at their children, providing a nice gateway for your children to not just depend on this core object, but to their kin.

Good design is predicated on the concept that things only need to know about what they need to do their single responsibility. Do your components need the entirety of this root God object to function? I sincerely doubt it.

Instead, have these components declare what they need, and let the code that builds/assembles/uses them supply the thing that satisfies that need. This way, you get all of the benefits of proper decoupling - testability since tests can pass in different implementations, maintainability since the component only worries about itself and changes to other stuff does not impact it, and flexibility since different consumers can tweak the object specifically to their needs.

8
  • Thanks for the answer. I have to confess I don't see how this is workable. For example, if I have a Properties window that displays the properties of another window, then if that has no knowledge of it's container how is it supposed to know about the other ones? I suppose I have a slightly "old school" mentality in how I learned this stuff original and tend to focus more on technical aspects rather than design patterns which is causing problems but to be honest nothing like this is covered in any of the 3 dozen programming books I've gathered over the years. Jan 5, 2014 at 4:45
  • 1
    The reference in and of itself does not preclude re-use in other environments. It just offers the API user a convenient way to get back to the root and access other parts of the API from there. Re-use is / would be precluded if the child would actually call methods on the root reference. That would indeed create implicit dependencies that you might not be aware of and that would be better handled through dependency injection to make them explicit. Jan 5, 2014 at 11:49
  • @RichardMoss - It doesn't need to know about it's container, it needs to know about the thing it's displaying properties of and that's all.
    – Telastyn
    Jan 5, 2014 at 15:06
  • 1
    @MarjanVenema - Sorry, it does not preclude reuse, it just requires you pull along this root object that is almost guaranteed to have no use/application in your new app... To me that is effective enough to preclude reuse.
    – Telastyn
    Jan 5, 2014 at 15:07
  • @Telastyn: Yeah, if you are going to pass around classes (objects). There is no problem if you pass around a generic IApplication interface that various applications can extend into IWhateverApplication and clients investigate to see whether the IApplication reference they received also supports IWhateverApplication. Which it probably does, because clients generally know what application they are calling stuff on, so they would know that the IApplication is also an IWhateverApplication and could simply use a "soft" cast. (app as IWhateverApplication). Jan 5, 2014 at 17:14
0

What you said in your comment has some importance. Maybe you could design some kind of hierarchy. Instead of everything referencing the application, have it reference some kind of parent object in hierarchy. Application can have UI windows/frames, those have controls. App can have documents, that too have some children, etc.. Maybe you could create something much more specific than everything referencing the application root object.

I would also agree with Telastyn. The object should have reference to it's parent if it uses this parent in some way. Don't go blindly add parent references to every object even if it doesn't use it.

Also, if you are thinking about memory consumption and counting bytes, then .NET is not a good pick.

Edit: After reading your comments, there are few things I would like to reply to:

About the consistency of traversal. Just because the object has reference to it's parent doesn't mean it has to expose it. Usually, such information is implementation detail. So from the outside, all objects are the same. From the inside on the other hand, there is problem with your mentality. You should think of object in almost complete isolation. Thinking that "when X object have Y, then all objects should have Y, because it is consistent" is not good, because it increases coupling and decreases cohesion.

"what's really so wrong with asking the application what the active document is and then doing something with said document?" Because it doesn't clearly express intent of the class, that it is requiring data that changes. If for example you used events and there was DocumentChanged event, that the class subscribes to and is pushed the new document, then that clearly states the intent of the class.

Finally, about your comment about technical stuff vs. patterns. Design patterns and principles are clearly technical thing. They are way to structure your code in a way that clearly expresses their intent and allows for easy maintenance and modification. If they weren't mentioned in any of those books, then those books are probably quite bad. Also, I recommend reading : What if I will not use Software Design Patterns?

And some nitpick: True engineer knows many tools, their strengths and weaknesses and knows when to use one over another. Saying you are fine with working only with one makes you look like a stupid code monkey instead of engineer.

1
  • Thanks for the comment. Everything is nicely arranged in a hierarchy, the object models are usually logical even if they have a messy implementation. I normally do add parent references, although occasionally they aren't really needed. But again, I like consistency and if one object can travel back and one can't, you can guarantee you'll need to on the one that can't. As for language choice, you're probably right but I've been working with .NET for many years now and I can't see that changing anytime soon! Jan 5, 2014 at 4:50
0

You can try abolishing the Application altogheter, which means now some of your objects would be unable perform their job, having no way of reaching their colaborators.

Then, you start passing along only the objects really needed for your object to perform its work. Say, using your example of a "Paste" plugin (in the comment here), it needs a document in order to do his job, so you pass a document.

I think this is referred as the Law of Demeter, ask you want, not the thing on which you will find the thing you want, i.e. only depend on things really related to your single responsability.

When you pass the application to your plugin, you're unnecessarily coupling things togheter. If for some reason you'd want to change the active document discovery mechanism, you'd be in risk of breaking some plugin. Now, if you have hundreds of plugins depending on things like that, you end up wasting a vast amount of time fixing them, or you don't change it, which means you can't refactor, and the codebase may rot by workarounds.

Of course, in the example of your plugin maybe you need some other things, and maybe you would need some kind of uniformity. Still you don't need to pass along the full aplication, you can define an interface with only the things plugins (or a certain type of plugin) needs, and then use it (Interface Segregation Principle).

Let's say, your application has this methods:

interface Application {
    void quit();
    void openSaveAsDialog();
    Document getActiveDocument();
}

If your plugins are not intended to use quit or openSaveAsDialog, then you can define something like:

interface Benchwork {
    Document getActiveDocument();
}

For the plugins, you can still implement Benchwork in terms of calling Application, but you can probably break Application in several smaller pieces (several nested subsystems, like a bench, containing a display, containing views, one of which is as an editor which deals with documents, composed of elements (images/paragraphs/attachments) etc), and have Application to be a really thin high-level thing.

Not an architect myself, but the downside I can see, is that you will probably end up with far more classes and interfaces in your project, but as they make things more manageable, not sure if that is really a problem.

You may want to check the google clean code talks (which is mostly about dependencies and how to make them testable).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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