So OOP is about breaking down functionality, making each class responsible for one thing etc. But let’s take the example where an object is using another object. First thing that comes to mind “composition, of course!”.
I have 2 questions:
Question 1:
Suppose you have a Shop that is loading products from a file using a ProductsLoader, which in term uses a ProductsFileReader. You design the API so that the code is simple and readable and specialized. Therefore, you are able to do this:
std::shared_ptr<Shop> shop = std::shared_ptr<Shop> (new Shop());
shop->LoadProducts();
// other code
In LoadProducts
you do this:
m_productsLoader.LoadAllProducts();
std::shared_ptr<Products> products = m_productsLoader.GetAllProducts();
this->setProducts(products);
In LoadAllProducts()
you do this:
prodFileReader.OpenProductsFile();
prodFileReader.LoadDescriptionsFromFile();
prodFileReader.CloseProductsFile();
// create products from descriptions
std::shared_ptr<Descriptions> descriptions = prodFileReader.GetProductsDescriptions();
this->createProductsFromDescriptions(descriptions);
And now here is the question: Do you keep ProductsLoader m_productsLoader
as a Shop
member? The same for ProductsFileReader
: should it be a member of ProductsLoader
? The alternative for this is creating these objects on the stack: when your functions are using one of them, you just declare, initialize etc them in the function and they get destructed when the function exits. Does this defy the purpose of OOP?
Question 2:
Let’s say you decided to keep them as members. How do you know wheter you should keep the whole object as a member, or you should keep only a pointer to it (and, of course, in the constructor, instantiate the member and assign the pointer). Whether you access the member with the dot opperator .
or arrow ->
, it will perform the same functionality.
I would usually keep the collaborator objects as pointer members in the object that will use them. I would also usually have them implemented using an abstract class and pass the pointers as parameters to Shop’s constructor rather than having Shop building them itself. This decouples Shop from the detail of how products are loaded, and means I can change to a different source of Products without needing to modify Shop; this is the fundamental essence of the Open/Closed principle, which some people describe as the most important principle on object-oriented design.
1
The subject you’re asking about is called modeling, and it’s as much an art as a science. But the first step of OO modeling is to forget about the mechanics of the language and think about the properties of the things you’re modeling.
A class named ProductsLoader
is suspect to begin with. Names like that indicate that you’re thinking procedurally instead of in an OO way. Instead, the Product
class should have functions to produce instances and collections of itself from files or streams or whatever.
So back to your question, but substituting Product
for ProductsLoader
as the example. How to decide whether Product
(or a collection of them) should be a member of Shop
? Well, do shops have products in them? That’s the kind of question that should guide your decisions in OO modeling, not how the particular language you’re using allocates things.
3