C++ Object-Oriented Design – Separate Classes for Building and Using an Object

cclassobject-oriented

Suppose I'm writing some C++ code to visualize "Foo" objects. I have two ways of getting a "Foo": computing it from data, or from taking the pieces of a precomputed "Foo" and building a new "Foo".

Now, once a "Foo" is computed it's guaranteed to be good for visualization, but changing it may break this assumption. Therefore, I've decided to represent "Foos" in my code by a Foo class that has no mutating methods: once it is constructed and initialized, it doesn't change.

But there's a second way to make a "Foo": build it from a precomputed "Foo"'s components. I've come up with several methods of building a Foo from precomputed data:

Method 1: Constructor/Static methods

Perhaps the most obvious method would be to add a new constructor or a static method to Foo, call itfromPrecomputed, that would read the components of the precomputed Foo and make a new Foo object, checking that it is valid. To explain why I'd like to shy away from this, I have to complicate my example: Let's say that one component of a "Foo" is a collection of "Bars". Now, in terms of implementation, sometimes a "Bar" is represented as a std::vector<std::vector<Bar> >, sometimes as a Bar array[][2], sometimes as a std::vector<std::pair<Bar,Bar> >, and so on… I could have the user reorganize their data into a standardized form and have a single constructor for this standard, but this might require the user to perform an extra copy. I don't want to provide a static method for each format: readPrecomputedFormatA, readPrecomputedFormatB, and so on: this clutters the API.

Method 2: Make Foo mutable

If I exposed the addBar(Bar) method of Foo, then I could allow the user to iterate over their collection of "Bars" in their own way. This, however, makes Foo mutable. So I could compute a Foo that makes sense for visualization, then use addBar to add a Bar that makes the Foo no longer a "Foo". Not good.

Method 3: Make a friend "builder" class

I make a class called FooBuilder which has the addBar(Bar) method exposed. I make FooBuilder a friend of Foo and add a constructor to Foo that takes a FooBuilder. On calling this constructor, it checks to make sure that FooBuilder contains a valid "Foo" object, then swaps its empty representation of a Foo with what is inside the FooBuilder. Everybody is happy.

The only "messiness" about method #3 is that it requires a friendship, but it's worth it to maintain encapsulation I think. But this has got me thinking: is this an established pattern? Or is there another, better way of doing this that I don't know about?

Best Answer

What you consider FooBuilder and Foo is actually part of a well-established Builder pattern. The other approach you mention with creating a Foo instance based on an existing instance is called a Prototype pattern. Both of these are well-known object creation alternatives and several books (most notably "Design Patterns", aka GoF) have been written describing them. (not sure I'm clear on your second mutable example, so comparing #1 to #3 here)

Each pattern obviously has its own advantages and drawbacks and only you can decide which one is better for your specific situation. For example, I would typically use builders when there's a lot of different ways of initializing Foo. For example, right now I'm working on a query builder class and many clients use it differently to build query objects. Different clients want different fields to be returned, some clients want only last 10 rows, some want sorting while others don't. Builder is perfect as it can look something like this:

qb = QueryBuilder()
query = qb.fields("name", "address")
          .sort("zip", "asc")
          .limit(10)
          .build()

So multiple setters, each returning instance of the builder itself so you can daisy-chain basically serve as a very flexible constructor. Making one class a friend of another class has it's time and a place and potentially this could be one of those places.

Alternatively, you could define Foo as having one constructor that takes a rather complex set of params, or potentially you could define another class FooData, that FooBuilder could initialize and pass into Foo creation. Then again, maybe Foo and FooBuilder being friends in this case isn't such a bad thing. Just keep in mind that when one class is a friend of another one, you are expanding encapsulation boundary, which could be just as bad as putting more and more code all within one class (i.e. more code knows about internals)

At the end of the day, you could be thinking and considering all these different options and it will all boil down to 60/40. If there's no clear winner, you can always let a coin pick the winner and just go with it. Most likely either one of the design choices would work just fine and by going through the motions and seeing your code in action you will learn valuable lesson for future. Sometimes when I have design decisions and can't decide between A and B, I could end up picking B and then if I ever come across a similar situation I would intentionally pick the other choice. The work in either case gets done and I get the software to do what I want, but you get a ton of benefit of actually seeing your decisions in action and being able to compare the two working approaches, rather than just discussing and thinking about them.