google/fruit

Defining Factory Functions for Objects with Multiple Constructors

IrisPeter opened this issue · 11 comments

When I originally needed to create a DI Object which would be passed some arguments I was guided by the "Scaling Doubles Injection Test", and looked at ScalerFactory, however my object didn't have any additional dependencies, so I had no substitute for the Multiplier*, and so I just removed it.

In the old Business Tax Objects system, some of the objects have multiple ::Create methods, so far I've been translating objects that had only one Create Method and therefore also only one private Constructor and so this hasn't been a limitation, when working with fruit.

The object I'm trying to create receives a Factory, as far as I can see, just as you have a Multiplier* parameter in your constructor, I can substitute it for a SomeObjectFactory parameter

using SomeObject = std::unique_ptr<ISomeObject>;

using SomeObjectFactory = std::function<SomeObject(int)>;

However I am unsure on what step to do next to support multiple constructors.

So far I see that there is a 1 to 1 relation ship between constructors and getXXXComponent functions

Does this mean I will need two sets of getXXXComponent functions?

using SomeObjectFactory = std::function<SomeObject(int)>;
using SomeObjectFactoryVer2 = std::function<SomeObject(std::string, int, double)>;

fruit::Component<SomeObjectFactory> getSomeObjectOneParam();
fruit::Component<SomeObjectFactoryVer2> getSomeObjectThreeParams();

That's an option if you prefer to keep them separate (which might be a reasonable desire depending on the domain).
You also have the option of having a single get*Component function with multiple provided types, e.g. all factories for that type.
That would look like:

using SomeObjectFactory = std::function<SomeObject(int)>;
using SomeObjectFactoryVer2 = std::function<SomeObject(std::string, int, double)>;

fruit::Component<SomeObjectFactory, SomeObjectFactoryVer2> getSomeObjectComponent();

@poletti-marco brilliant, your suggestion above is what I was hoping for.

Hi @poletti-marco

I should have also asked how do you create the 2nd constructor in the class. I think it would be helpful if the Wiki was updated to include these more complicated scenarios.

Maybe another example could be added to the examples folder.

I should have also asked how do you create the 2nd constructor in the class.

If you want 2 constructors you should use registerFactory as opposed to INJECT+ASSISTED, see this doc section:
https://github.com/google/fruit/wiki/quick-reference#factories-and-assisted-injection

Then it doesn't matter how many you have, you just add 1 registerFactory call for each constructor.

I think it would be helpful if the Wiki was updated to include these more complicated scenarios.

There is a balance in the documentation, between not having enough detail and overwhelming Fruit users looking for something simple with every possible use case.

From the discussion in the various recent threads it sounds like y'all are not trying to use Fruit in the usual ways, which is ok and I'm going to help you with this anyway, but I don't think we should expose all users looking for Fruit documentation to this sort of very specific use cases. If other people are doing something obscure chances are that it's subtly different from what y'all are doing, so they'll need to open an issue to discuss anyway (or worse, see the solution for this case there and assume it should apply to theirs too even if it doesn't).
For this question specifically, I think the registerFactory section already shows what to do, but AFAICT this comment was more general, so this reply is more about the rest.

I'm not sure I'm doing anything that special, Dependency Injection is surely just a technique to allow us to create classes that receive other objects that they depend on?

So if classes in general often have multiple constructors, then surely adding in the Dependency Injection should be agnostic to that?

I did finally manage to get a variation on the "Scaling Doubles Injection Test" to work with an additional constructor.

https://github.com/IrisPeter/MinimalFruitExample (scaler.h/cpp multiplier.cpp, entry point is in MinimalFruitExample.cpp, project is a VS Solution, no CMake)

The other thing I have been trying to do which is caching database data in memory also doesn't seem to be something remarkable.

I'm ready to close this issue, I will keep it open until Monday in case there is anything you want to add before I do that

I'm not sure I'm doing anything that special, Dependency Injection is surely just a technique to allow us to create classes that receive other objects that they depend on?

Yeah, it's just that (at least in my experience) usually DI is used to compose modules of a system vs for "data" objects whose number and relationships are fully dynamic at runtime.

So if classes in general often have multiple constructors, then surely adding in the Dependency Injection should be agnostic to that?

If using DI for modules, the constructor will list the dependencies of that module, which are usually a fixed set of interfaces, and then the difference is on which implementation of those are used in the system.
It's like a UML component with plugs that can then be assembled with other similar components, like the diagrams in https://github.com/google/fruit/wiki/tutorial:-getting-started and other pages.

I did finally manage to get a variation on the "Scaling Doubles Injection Test" to work with an additional constructor.
https://github.com/IrisPeter/MinimalFruitExample (scaler.h/cpp multiplier.cpp, entry point is in MinimalFruitExample.cpp, project is a VS Solution, no CMake)

Nice, glad that you got that to work!

The other thing I have been trying to do which is caching database data in memory also doesn't seem to be something remarkable.

It's not uncommon to cache data, but usually (from what I've seen) DI isn't used for that.
E.g. you might have a storage layer and an RPC layer on top and use DI to compose those modules (which allows you to e.g. swap the storage layer for a fake one in tests) and part of that might be a FooCache class, but if I were to design such a system I probably wouldn't use DI to create each of the "data" objects (e.g. representing what you read from a row in the DB).

That said, maybe that's just what I'm used to, and if you find Fruit useful to create data objects too you're free to use it for those too.

It's not uncommon to cache data, but usually (from what I've seen) DI isn't used for that.
They are just Business Objects, they don't necessarily cache or load anything but the original base class allowed for that.

Indeed there are a load of objects that just have stubbs for all the loading and saving functionality, so when I've been redoing them I've been using a more simplified interface.

They often have things like special calculations, so they aren't exclusively POCO/POD objects. Sometimes they generate descriptions for UI elements, or Report Titles based on whether a period falls under the dates of new legislation. Other objects map legislation dates against enum values, so that you can query whether a type of legislation applies to the selected time period.

E.g. you might have a storage layer and an RPC layer on top and use DI to compose those modules (which allows you to e.g. swap the storage layer for a fake one in tests)
Yes, what I want to do is swap out the DB layer, for a JSON one under testing.

I've been reading through "Dependency Injection - Principles, Practices and Patterns" by Seemann/Dan Deursan in order to learn more and hopefully see how I should formulate this swappable interface, now that I'm on chapter 5 I'm seeing things like IProductRepository so I think I'm nearing the kind of stuff I would need to know.

I don't know if the swappable interface should just contain the members that the original base class had or whether it should manipulate reading and writing key value pairs.

class IObjectLoader
{
    virtual ~IObjectLoader = default;
    void Load() = 0;
    bool Save() = 0;
    bool IsPopulated() const = 0;
};
class IDBLoader : public IObjectLoader
{
};
class IJSONLoader : public IObjectLoader
{
};

Then for an object you would have:

class StatementImpl : public IStatement 
{
private:
    IStatementLoader* IStatementLoader;
    int clientId;

public:
    INJECT(StatementImpl(ASSISTED(int) clientId, IStatementLoader* IStatementLoader)) : IStatementLoader(IStatementLoader), clientId(clientId) {}
};
class IStatementLoader : public IObjectLoader
{
public:
   int member1;
   bool member2;
        ....
}
class StatementJsonLoaderImpl : public IJSONLoader
{
public:
   INJECT(StatementJsonLoaderImpl())
   {
   }
    void Load() override { ... }
    bool Save() override { ... }
    bool IsPopulated() const  { ... }
};

class StatementDBLoaderImpl : public IDBLoader
{
public:
    INJECT(StatementJsonLoaderImpl())
    {
    }
    void Load() override { ... }
    bool Save() override { ... }
    bool IsPopulated() const  { ... }
};

I would generally recommend doing the switching between the *JsonLoader and *DBLoader at as low a level as possible, to maximize the amount of real code that is being tested when using the JSON loader.

Ideally, if the DB library you're using implements a pure virtual interface that you can implement in the fake JSON-based DB then I would fake at that level so you can have a single fake instead of 1 for each table.
But it depends on how simple the DB API is, if it's too complex then it could justify faking at a higher level; I'd still lean on having a single fake if possible, and only if that's unworkable then going further higher-level to entity-specific loaders as in your example.

I would generally recommend doing the switching between the *JsonLoader and *DBLoader at as low a level as possible, to maximize the amount of real code that is being tested when using the JSON loader.

On Saturday I had a go at getting this working, and I decided that both IJSONLoader and IDBLoader were totally pointless, and so instead I was just going to have the two substitutable implementations of IObjectLoader, one standard object named DB[ObjectName]Loader, and one to substitute with named JSON[ObjectName]Loader

e.g.

class DBStatementImpl : public IStatement 
{
private:
    IObjectLoader* IStatementLoader;
    int clientId;

public:
    INJECT(StatementImpl(ASSISTED(int) clientId, IStatementLoader* IStatementLoader)) : IStatementLoader(IStatementLoader), clientId(clientId) {}
}

Ideally, if the DB library you're using implements a pure virtual interface that you can implement in the fake JSON-based DB then I would fake at that level so you can have a single fake instead of 1 for each table. But it depends on how simple the DB API is, if it's too complex then it could justify faking at a higher level; I'd still lean on having a single fake if possible, and only if that's unworkable then going further higher-level to entity-specific loaders as in your example.

Unfortunately the codebase although it is graphical, it dates back to DOS days and was built on a ISAM database, these days it is a proper relational database and so there is an emulator that converts the old ISAM API calls into SQL and so there are various low level DB APIs that are used depending on the table, and yet other parts of the codebase use the Entity Framework.

Most of the time when the Business Object needs to load itself from the database there are various Data Access objects and depending on when they were written in the life of the codebase they have different APIs (some objects which are hand written, and other are generated from XML Schema files) which the business object just includes by composition and then the data access object loads the required data.

depends on how simple the DB API is

How do these simple APIs look, I have no idea what that would look like, would the interface have gone down to the field leve?

class DBInterface
{
   virtual void AddField(const std::string &fieldName, enum class FieldType) = 0;
   virtual void ExecuteQuery() = 0;
            ...
};

The example I got working was based on the Scaler class within the Scaling Doubles Test

ScalerLoader.cpp

#include "IObjectLoader.h"

class IScalerLoader : public IObjectLoader
{
public:
    virtual double GetFactor() const = 0;
    virtual void SetFactor(double fac) = 0;
};

JSONScalerLoader.cpp

#include <json.hpp>
class JSONScalerLoaderImpl : public IScalerLoader
{
public:
    INJECT(JSONScalerLoaderImpl()) = default;

    void Load override
    {
	// Some file
	//m_objectRepresentation = json::parse(objectJSON);
        m_Factor = 5.5;

        m_IsPopulated = m_Factor != 0.0;
    }

    void Save() override
    {
        return false;
    }

    bool IsPopulated() const override
    {
        return m_IsPopulated;
    }
 private:
    bool m_IsPopulated = false;
    double m_Factor = 0.0;

    double GetFactor() const override
    {
        return m_Factor;
    }
    void SetFactor(double factor) override
    {
        m_Factor = factor;
    }

    nlohmann::json m_objectRepresentation
};

C# seems to have many more examples of DI in practice, but I haven't found anything I could base my new code off, the only thing I could think of was just producing an interface for the getters and setters for the fields of an object I want to load.

I don't think I've got far enough in "Dependency Injection - Principles, Practices and Patterns" to see how else to do, as so far its turtles all the way down, I haven't got to any object that will actual load a repository etc.

At the moment something like the above JSONScalerLoaderImpl would do, as in the DB[ObjectName]Loader I can just put one of the Data Access objects in there, and just map from each field to an interface with a get and set for each subpart of the object.

these days it is a proper relational database and so there is an emulator that converts the old ISAM API calls into SQL and so there are various low level DB APIs that are used depending on the table, and yet other parts of the codebase use the Entity Framework.

This sounds like a really complicated architecture, I guess it's up to you at what point you want to fake out stuff.
Usually there is a single low level DB API used for everything.

You mention an emulator but it's not clear if that's part of your codebase or just sth that you're using.
If the former I'd fake out after that (i.e. run the emulator in JSON-based tests too, and implement only the SQL API surface in the fake). If the latter then I'd fake out before that (i.e. don't run the emulator in JSON-based tests, instead implement the ISAM surface too in the JSON fake).
But it's a bit of a grey area either way.

How do these simple APIs look, I have no idea what that would look like, would the interface have gone down to the field leve?

Maybe? I would fake out at the layer where the lowest level of your code then calls some database library.
Depending on what library you're using that API might look very different.
I'd use a similar API (maybe slightly generalized) as the one where you do the switching between real and fake implementation.