Skip to main content
edited body
Source Link
jme
  • 559
  • 1
  • 5
  • 13

Suppose I'm writing soemsome 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?

Suppose I'm writing soem 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?

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?

Source Link
jme
  • 559
  • 1
  • 5
  • 13

Is it good or bad practice to provide separate classes for an object: one to build it, and one to use it?

Suppose I'm writing soem 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?