-
Notifications
You must be signed in to change notification settings - Fork 200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Defining Factory Functions for Objects with Multiple Constructors #152
Comments
That's an option if you prefer to keep them separate (which might be a reasonable desire depending on the domain).
|
@poletti-marco brilliant, your suggestion above is what I was hoping for. |
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. |
If you want 2 constructors you should use Then it doesn't matter how many you have, you just add 1
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). |
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 |
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.
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.
Nice, glad that you got that to work!
It's not uncommon to cache data, but usually (from what I've seen) DI isn't used for that. 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. |
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.
|
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. |
On Saturday I had a go at getting this working, and I decided that both e.g. class DBStatementImpl : public IStatement
{
private:
IObjectLoader* IStatementLoader;
int clientId;
public:
INJECT(StatementImpl(ASSISTED(int) clientId, IStatementLoader* IStatementLoader)) : IStatementLoader(IStatementLoader), clientId(clientId) {}
}
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.
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. |
This sounds like a really complicated architecture, I guess it's up to you at what point you want to fake out stuff. You mention an emulator but it's not clear if that's part of your codebase or just sth that you're using.
Maybe? I would fake out at the layer where the lowest level of your code then calls some database library. |
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 theMultiplier*
, 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 aSomeObjectFactory
parameterHowever 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
andgetXXXComponent functions
Does this mean I will need two sets of getXXXComponent functions?
The text was updated successfully, but these errors were encountered: