Dependency injection with factory class

2019-04-15 05:09发布

We have a request based application where requests are executed by sending a command over serial.

When data is received indicating a request is to be performed the request is created using a factory class by specifying that requests specific ID.

The problem is that each request will have different dependencies depending on the tasks it has to perform and I am looking for the best solution to do this.

What is the best way to inject dependencies to requests when dependencies may differ between requests?

Is passing a reference to the RequestFactory for every possible request dependancy a bad idea? (We currently have around 20-30 different requests that have around 6 or 7 different dependencies in total)

My planned solution is similar to that below, but is there an easier, or better approach?

Thanks.

class Request;
class RequestOne;
class RequestTwo;

class RequestFactory
{
public:
  RequestFactory( /* Dependencies for RequestOne and RequestTwo */ )
  {
    // Keep reference to dependencies
  }

  std::shared_ptr< Request > create( int id )
  {
    std::shared_ptr< Request > request;

    switch ( id )
    {
    case 1:
      request = std::make_shared< RequestOne >( /* RequestOne Dependencies */ );
      break;
    case 2:
      request = std::make_shared< RequestTwo >( /* RequestTwo Dependencies */ );
      break;
    }

    return request;
  }
};

class Request
{
public:
  virtual ~Request(  );
  virtual void process(  ) = 0;
};

class RequestOne : public Request
{
public:
  RequestOne( /* RequestOne Dependencies */ )
  virtual ~RequestOne(  );
  virtual void process(  );
};

class RequestTwo : public Request
{
public:
  RequestTwo( /* RequestTwo Dependencies */ );
  virtual ~RequestTwo(  );
  virtual void process(  );
};

2条回答
Rolldiameter
2楼-- · 2019-04-15 05:48

It sounds like you're primarily concerned about the number of constructor parameters that would need to be supplied to RequestFactory (i.e., the union of the dependencies of all products). You can handle this situation in the same way you handle others in which a class has a large number of dependencies: identify new collaborators for the class.

As a class gathers more and more dependencies/collaborators, patterns tend to emerge between some of those dependencies. These patterns almost always represent some previously-unidentified abstraction(s). If you can place a name on such an abstraction, you can refactor the class to use it in place of the "related" dependencies.

Mark Seemann has referred to this as Refactoring to Aggregate Services.

Your RequestFactory seems like a good candidate for this. Consider how things might look if the RequestFactory class collaborated with two other classes:

class Request;
class RequestOne;
class RequestTwo;

class RequestOneFactory
{
public:
    virtual std::shared_ptr< RequestOne > CreateRequest(/* RequestOne Dependencies */) = 0;
};

class RequestTwoFactory
{
public:
    virtual std::shared_ptr< RequestTwo > CreateRequest(/* RequestTwo Dependencies */) = 0;
};

class RequestFactory
{
public:
  RequestFactory(std::shared_ptr< RequestOneFactory > requestOneFactory, std::shared_ptr< RequestTwoFactory > requestTwoFactory)
  {
    // Keep reference to collaborating factories
  }

  std::shared_ptr< Request > create( int id )
  {
    std::shared_ptr< Request > request;

    switch ( id )
    {
    case 1:
      request = requestOneFactory->CreateRequest();
      break;
    case 2:
      request = requestTwoFactory->CreateRequest();
      break;
    }

    return request;
  }
};

Looking at things this way, we might start to suspect whether RequestFactory was actually taking on multiple responsibilities:

  1. Determine what type of request to create
  2. Create that type of request

By refactoring the code, RequestFactory maintains the first responsibility while delegating the other to collaborating classes.

Note: Using the above approach, it's possible for the abstract factories to be too heavily influenced by the concrete request classes, which could be a code smell. However, I suspect that RequestOne and RequestTwo may represent distinct abstractions in their own right, which would make the introduction of abstract factories much more logical.

查看更多
Juvenile、少年°
3楼-- · 2019-04-15 05:59

The solution proposed by @Lilshieste has the same flaw of your original implementation, you have to manually maintain a "switch-heavy" statement, even worst, in @Lilshieste solution you have increase the number of factories given as parameter to the RequestFactory.

Since you don't mentioned any performance issue I'll propose a little slower, but more solid solution.

I do not agree that this need aggregation, the reason is that in your case you just don't have many dependencies, you need polymorphic behaviour, wich does not require aggregation but construction of an appropiate interface to deal with.

Observation 1: Since you are using C++11, use the new enums instead of "int" for the ID (that will give usefull compile time errors).

Observation 2: Reverse the problem. Dont let the generic RequestFactory depends on Concrete Factories, instead let ConcreteFactories register themselves in the RequestFactory!

class RequestOneFactory: public virtual AbstractRequestFactory{
    //note: no member variables! 
public:
    RequestOneFactory( std::shared_ptr<RequestFactorySupplier> factory){
        factory->register( getptr(),ID);
    }

    std::shared_ptr< Request> create() const{
        std::shared_ptr< Request> request =
            std::make_shared< RequestOne>( /* Dependencies*/);
        return request;
    }

};

Every Factory have the same Interface, since you are doing DI, you probably want the factory to be managed. Just inherit from enable_shared_from_this.

class AbstractRequestFactory: std::enable_shared_from_this< AbstractRequestFactory>{
public:
    virtual ~AbstractRequestFactory(){}

    virtual std::shared_ptr< Request> create() const = 0;

    std::shared_ptr<AbstractRequestFactory> getptr() {
        return shared_from_this();
    }
};

The RequestFactory has now no constructor dependencies, also note that I broke up its interface in 2 parts so that different users of the factory can do different things.

#include <unordered_map>
#include <RequestFactorySupplier.hpp>
#include <RequestFactoryUser.hpp>
#include <AbstractRequestFactory.hpp>

class RequestFactory: public virtual RequestFactorySupplier
                     ,public virtual RequestFactoryUser{
    //associative container.
    std::unordered_map< RequestID, std::shared_ptr< AbstractRequestFactory>> map;

public:
    RequestFactory()=default;
    ~RequestFactory()=default;

    //IMPLEMENTS: RequestFactorySupplier
    virtual void register( std::shared_ptr< AbstractRequestFactory> fac, RequestID id){
         if(map.find(id)!=map.end()){
              //ID already used.. throw error, assert(false), what you want.
         }
         map[id] = fac;
    }

    //IMPLEMENTS: RequestFactoryUser
    virtual std::shared_ptr< Request> create(RequestID id){
         if(map.find(id)==map.end())
              throwSomething(); //No factory for such ID
         return map[id]->create();
    }
};

If you are using a Dependency Injection framework

now the work to wire up everything is very simple, the following example uses Infectorpp (wich I wrote), you can do similiar things with other frameworks of course.

#include <Infectorpp/InfectorContainer.hpp>

int main(){
Infector::Container ioc;

//register generic factory with 2 interfaces
ioc.bindSingleAs<RequestFactory,    RequestFactorySupplier,RequestFactoryUser>();

//for each concrete factory register it
ioc.bindSingleAsNothing<RequestOneFactory>();
ioc.bindSingleAsNothing<RequestTwoFactory>();
//...

//wire the generic factory
ioc.wire<RequestFactory>();

//wire the factories (dependencies injected here)
ioc.wire<RequestOneFactory,    RequestFactorySupplier>();
ioc.wire<RequestTwoFactory,    RequestFactorySupplier>();
//...

//build the factories (let them register in RequestFactorySupplier)
{
    ioc.buildSingle<RequestOneFactory>();
    ioc.buildSingle<RequestTwoFactory>(); 
    //you will not have references to them but RequestFactory will: Good!
}

//Your classes can use RequestFactoryUser to create RequestObjects.

//application run!

return 0;
}

Also consider creating objects wrapped around a std::unique_ptr for those factories instead of a std::shared_ptr (if unique_ptr is enough never use std::shared_ptr).

查看更多
登录 后发表回答