views:

204

answers:

6

Hey all -

So I'm working to improve an existing implementation. I have a number of polymorphic classes that are all composed into a higher level container class. The problem I'm dealing with at the moment is that the higher level container class, well, sucks. It looks something like this, which I really don't have a problem with (as the polymorphic classes in the container should be public). My real issue is the constructor...

/*
 * class1 and class 2 derive from the same superclass
 */
class Container 
{
  public: 
   boost::shared_ptr<ComposedClass1> class1;  
   boost::shared_ptr<ComposedClass2> class2;
  private:
   ...
}

/*
 * Constructor - builds the objects that we need in this container. 
 */ 
Container::Container(some params)
{
  class1.reset(new ComposedClass1(...));
  class2.reset(new ComposedClass2(...));
}

What I really need is to make this container class more re-usable. By hard-coding up the member objects and instantiating them, it basically isn't and can only be used once. A factory is one way to build what I need (potentially by supplying a list of objects and their specific types to be created?) Other ways to get around this problem? Seems like someone should have solved it before... Thanks!

+2  A: 

Dependency injection springs to mind.

William Billingsley
thanks for the link. in reading the link, I'm pretty sure this is what i'm looking for, hence you win! :-) thanks much.
jdt141
A: 

You use a factory method if you change the created type in derived classes, i.e. if descendants of Container use different descendants of class1 and class2.

class1* Container::GetClass1; virtual;
{
  return new ComposedClass1;
}

class1* SpecialContainer::GetClass1;
{
  return new SpecialComposedClass1;
}

Container::Container(some params)
{
  class1.reset(GetClass1);
  class2.reset(GetClass2);
}

A different approach is decoupling creation of Container's dependencies from Container. For starters, you can change the constructor to dependency-inject class1 and class2.

Container::Container(aclass1, aclass2)
{
  class1.reset(aclass1);
  class2.reset(aclass2);
}
Ozan
This approach is normally fine, but you should not use factory methods in a constructor (constructors should not call virtual methods, as they are executing on a class before has finished constructing). I would recommend an abstract factory instead with dependency injection.
iain
A: 

You do know you can just have a second constructor that doesn't do that right?

Container::Container(some params)
{
    // any other initialization you need to do
}

You can then set class1 and class2 as you please as they're public.

Of course they shouldn't be but given this code that's what you could do.

As far as I can see from what you've described the factory pattern wouldn't be that helpful.

Tom
Sure, I could do that, but the problem is that a different instance of the container may not even need Class1 and Class2 composed into it. It may have Class3, 4, and 5 as objects composed into it. Trying to find a general way.
jdt141
A: 
public class factory
{
    public static Class1 Create()
      {
            return new Class1();
      }
}

class Class1
{}
main()
{
   Classs1 c=factory.Create();   
}
yapingchen
A: 

Based on your description I would recommend an Abstract Factory passed into the constructor.

class ClassFactory {
public:
   virtual ~ClassFactory
   virtual Class1 * createClass1(...) = 0;  // obviously these are real params not varargs
   virtual Class2 * createClass2(...) = 0;
};

class DefaultClassFactory : public ClassFactory {
    //  ....
};


Container::Container(
    some params, 
    boost::smart_ptr<ClassFactory> factory = boost::smart_ptr<ClassFactory>(new DefaultClassFactory))
{
  class1.reset(factory->createClass1(...));
  class2.reset(factory->createClass1(...));
}

This is a form of dependency injection without the framework. Normal clients can use the DefaultClassFactory transparently as before. Other clients for example unit tests can inject their own ClassFactory implementation.

Depending on how you want to control ownership of the factory parameter this could be a smart_ptr, scoped_ptr or a reference. The reference can lead to slightly cleaner syntax.

Container::Container(
    some params, 
    ClassFactory & factory = DefaultClassFactory())
{
    class1.reset(factory.createClass1(...));
    class2.reset(factory.createClass1(...));
}

void SomeClient()
{
    Container c(some params, SpecialClassFactory());
    // do special stuff
}
iain
A: 

Generally, I agree with Toms comment: not enough info.

Based on your clarification:

It's just a container for a number of "components"

If the list of components varies, provide Add/Enumerate/Remove methods on the container.

If the list of components should be fixed for each instance of the container, provide a builder object with the methods

Builder.AddComponent
Builder.CreateContainer

The first would allow to reuse the container, but often ends up more complex in my experience. The second one often requires a way to swap / replace containers but ends up easier to implement.

Dependency Injection libraries can help you configure the actual types outside of the aplication (though I still need to see why this is such a great leap forward).

peterchen