Home > Back-end >  Generic way of constructing objects
Generic way of constructing objects

Time:12-15

I have two classes:

class DoorRequest : IRequest
{
   string message;
}

class WindowRequest : IRequest
{
  string message;
}

They do not inherit from each other, but implement a common interface. I would like to have a generic way for creation of instances of these classes. Something semantically equivalent to:

public static T MakeOpenRequest<T>() where T : IRequest
{
  // If T is DoorRequest return new DoorRequest{message="please open the door"}
  // If T is WindowRequest return new WindowRequest{message="please open the window"}
}

What is important to me is that the result of the function is T, not IRequest. I can extend functionalities of DoorRequest and WindowRequest to provide actual implementations. The real-life logic of MakeOpenRequest can be imagined to be much more complex, so writing a plain factory would lead to massive code duplication. The only thing this method cares about is that it is supposed to create instances of some types in a named way, that is to be defined for each class separately.

I doubt regular constructors would help here, because there is hardly any genericity to be taken advantage of. Constructing methods must be static, so I can't really count on polymorphism on them.

What's the neatest way to tackle this task? Reflection would be cheating, but if there is no other way then it's fine.

CodePudding user response:

Generics requires the caller to know, in compile time, the actual type being used.
This means that you must call the method with the actual type you want it to use.
The generic constraint you're using only means your method can only handle types that implement the IRequest interface (or if it was a class, than types that are either IRequest or inherit from it).

That being said, If your types have a parameter-less constructor, you could simply add the new() generic constraint to the method and instantiate a new instance of T inside:

public static T MakeOpenRequest<T>() where T : IRequest,  new()
{
  var t = new T();
  // do stuff with t here...
  return t;
}

CodePudding user response:

The only way to instantiate a generic type is if the chosen type has a parameterless constructor. This also requires you to specify this as a generic type constraint.

Since you want your code to work agnostically of all possible types, your method cannot actually decide the correct message to be used (as this would require knowing the specific type, which leads to OCP violations). Therefore, the message should be defined in the concrete IRequest implementation itself.

However, this leaves your method itself with nothing to do other than call the constructor:

interface IRequest { }

class DoorRequest : IRequest
{
    public string Message { get; }

    public DoorRequest()
    {
        Message = "Please open the door";
    }
}

class WindowRequest : IRequest
{
    public string Message { get; }

    public WindowRequest()
    {
        Message = "Please open the window";
    }
}

static class Foo
{
    public static T MakeOpenRequest<T>() where T : IRequest, new()
    {
        var obj = new T();

        return obj;
    }
}

If you need the message to be decided in the MakeOpenRequest function and not in the IRequest implementation itself, and the message is specific to the type T being used, then the only possible solution is for MakeOpenRequest to change its behavior based on the specific type, which is not a good use case for generics. It is indicative of a bad design and is an OCP violation.

In this case, you should look for an alternative approach, because generics are not the way to go. Most likely, you'd be better off opting for specific methods (MakeDoorRequest, MakeWindowRequest), since it doesn't really matter to the consumer if they have to call MakeOpenRequest<WindowRequest> or MakeWindowRequest, since both require the same knowledge of concrete types.

You mention in your question that things might get much more complex. At this point, your should consider the factory pattern as opposed to trying to do the same with generics.

  • Related