Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I have several classes with a reasonably complex (but acyclic) dependency graph. All the dependencies are of the form: class X instance contains an attribute of class Y. All such attributes are set during initialization and never changed again.

Each class' constructor has just a couple parameters, and each object knows the proper parameters to pass to the constructors of the objects it contains. class Outer is at the top of the dependency hierarchy, i.e., no class depends on it.

Currently, the UI layer only creates an Outer instance; the parameters for Outer constructor are derived from the user input. Of course, Outer in the process of initialization, creates the objects it needs, which in turn create the objects they need, and so on.

The new development is that the a user who knows the dependency graph may want to reach deep into it, and set the values of some of the arguments passed to constructors of the inner classes (essentially overriding the values used currently). How should I change the design to support this?

I could keep the current approach where all the inner classes are created by the classes that need them. In this case, the information about "user overrides" would need to be passed to Outer class' constructor in some complex user_overrides structure. Perhaps user_overrides could be the full logical representation of the dependency graph, with the overrides attached to the appropriate edges. Outer class would pass user_overrides to every object it creates, and they would do the same. Each object, before initializing lower level objects, will find its location in that graph and check if the user requested an override to any of the constructor arguments.

Alternatively, I could rewrite all the objects' constructors to take as parameters the full objects they require. Thus, the creation of all the inner objects would be moved outside the whole hierarchy, into a new controller layer that lies between Outer and UI layer. The controller layer would essentially traverse the dependency graph from the bottom, creating all the objects as it goes. The controller layer would have to ask the higher-level objects for parameter values for the lower-level objects whenever the relevant parameter isn't provided by the user.

Neither approach looks terribly simple. Is there any other approach? Has this problem come up enough in the past to have a pattern that I can read about? I'm using Python, but I don't think it matters much at the design level.

share|improve this question
    
Is it vital that the correct override params are set at construction time, or is it allowed to change the properties after the whole graph has been created? –  occulus Dec 11 '12 at 10:40
    
Is there only one outer instance or are there many different instances of outer? An example of only one outer instance is a service-singelton that uses other sub-service-singletons to fullfill its job. An example of a multi instance whould be a car with tires. One car instance has summer-tires and an other instance has winter-tires. –  k3b Dec 11 '12 at 11:28
    
@occulus Was hoping to avoid mutability, but if it really helps I guess I can accept it. But why wouldn't I be able to do the very same solution you propose while everything is being built? –  max Dec 11 '12 at 11:40
    
@k3b - multiple instances of outer... I wouldn't want to assume it's a singleton. –  max Dec 11 '12 at 11:41
add comment

4 Answers

up vote 1 down vote accepted

You should look at all of the common object creation patterns and make an assessment based upon the state of your logic, time you have to implement the change, and so forth. Also, rather than have the participating objects know how to construct each other, you should consider Inversion of Control.

Its hard to be specific without more info, but given that you are talking about a complex graph I would think that Abstract Factory or Builder would be more useful here than just a simple Factory depending on how different each of your scenarios is.

If you have some initial decisions to make that influence creation of the graph but the differences are nuances, look at Builder...particularly if there are some structural differences between the "products" (such as, some participating objects are optional or in some scenarios participating objects drag in another set of collaborators).

If the differences between the scenarios are more distinct / elaborate and take the form of "families" of objects that should be used together, look at Abstract Factory with a few different factory implementations.

If you actually just have a few specific flavors of this graph and want to pick and choose between them dynamically, you might be able to just go with Prototype; treat your current implementation as a prototype, code up your alternate implementation(s) as another prototype, and just clone the appropriate prototype as needed.

share|improve this answer
    
Thanks I'll look at these patterns. Specifically with IoC, there's a lot of references that define it somewhat differently; if you have any specific link that describes precisely the pattern you have in mind, please let me know. –  max Jan 22 '13 at 5:29
    
Assuming I got the right definition of IoC, how can I apply to my problem? –  max Jan 22 '13 at 5:50
    
Sure, the Fowler link is good info on IoC. –  Ed Hastings Jan 28 '13 at 20:28
    
You say " each object knows the proper parameters to pass to the constructors of the objects it contains. " and some other exposition about wanting to "reach into" the logic to set or otherwise override behavior. In this context, one way to do a more IoC approach would be to instead have the inner logic call back or raise an event when it is time to make a decision about what it does next or when it needs something to move forward. Another IoC approach would be to inject an appropriate factory (or factories) or strategy or one of the other patterns mentioned previously into the inner logic. –  Ed Hastings Jan 28 '13 at 20:35
add comment

An approach using reflection might work well here.

So, assuming if it's ok to set up the graph in the default way, and then set values that you wish to override after (which requires mutability of your objects, obviously):

Have your object graph set up in the default way. Then use reflection to walk the object graph. For each object you find in the graph, check for any properties represented in your user_overrides helper structure for overriding. If you find a match, set the property to the new value (again, using reflection).

Depending on your exact scenario, you may just be able to match against class types in your user_overrides structure; alternatively, you might have to store keypaths, by which I mean something representing the route to a certain class in the object graph.

You may have to be careful about the order in which you override things in the object tree.

There's plenty of info out there about reflection.

share|improve this answer
    
Yeah I can try that, but it's a bit hard to change the design to allow the objects to mutate. –  max Jan 22 '13 at 5:29
add comment

I would move all object construction logic out of Outer into a different class using the Factory_pattern.

The Constructors of domain-classes ´Outer´ and ´Inner´ get all required child parameters

public Outer(Inner inner)
{
    _inner = inner;
}

and the factory create the items for you:

public class OuterFactory
{
    // Existing behaviour - concrete Inner instantiation is hard coded
    public virtual Outer createOuter(CreationParameters parameters)
    {
        Inner _inner = createInner(parameters);
        Outer outer = new Outer(inner);

        return outer;           
    }

    public virtual Inner createInner(CreationParameters parameters)
    {
        Inner _inner = new Inner();     
        return _inner;          
    }
}

If you need a different inner class you can easily create a new factory that derives from the base factory:

public class MySpecialOuterFactory : OuterFactory
{
    public overrrde Inner createInner(CreationParameters parameters)
    {
        Inner _inner = new SpecialInner();      
        return _inner;          
    }
}
share|improve this answer
    
"In @MattDavey example the class Outer knows how to create items of type Inner." um, no, in my example Outer is explicitly told how to create items of type inner. –  MattDavey Dec 11 '12 at 12:25
    
@MattDavey: you are right. i will fix my proposal-answer –  k3b Dec 11 '12 at 13:05
add comment

Your description of user_overrides is beginning to sound a bit like the strategy pattern - where objects outside of the hierarchy pass a strategy (callback) for creating the dependency..

example in pseudocode

type OuterType
{
    private inner : InnerType

    // Existing behaviour - concrete Inner instantiation is hard coded
    OuterConstructor()
    {
        _inner = new Inner()
    }

    // Strategy pattern, instantiation strategy is passed from outside as
    // a callback function which returns an Inner instance
    OuterConstructor(func() innerBuilderCallback)
    {
        _inner = innerBuilderCallback()
    }
}

What happens if Inner also has a dependency which you want to expose with an instantiation strategy? You don't have to pass both strategies to the Outer constructor, since that strategy will itself 'chain' the nested strategy

void Foo()
{
    func innerBuilderCallback = lambda: new Inner(lambda: new InnerDependency());

    var outer = new Outer(innerBuilderCallback);
}

Depending on how deep your hierarchy goes, you might find that you end up with quite a few of these callback strategies which becomes a little difficult to manage. In that case, encapsulate the whole process in a Factory object.

share|improve this answer
    
Interesting approach. What's the advantage of using strategies (callbacks that return an instance) instead of the instances themselves? –  max Jan 22 '13 at 5:26
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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