Chubsdad
Chubsdad

Reputation: 25497

Generic Factory with type parameter

I have the following situation.

My Factory class needs to create appropriate Strategy objects based on the input string argument to the CreateStrategy function.

Strategy1, Strategy2 etc are all derived from a common StrategyBase class. However each strategy has a different Validation mechanism which is the type parameter to the Factory class. However, the StrategyValidators are not of any common type and have different interfaces.

Therefore, in the below code, I am unable to specify any common constraint on the StrategyValidator type.

I am new to C# and hence not sure if there exists any mechanism to get over this design issue. Please suggest

public class Factory
{
    //Create the appropriate Concrete Implementation class based on the type
    public static StrategyBase CreateStrategy<StrategyValidator>(String Type)
    {
        StrategyBase EnumImp = null;

        // WMI based implementation
        if (Type == "Type1")
        {
            s = Strategy1<StrategyValidator>.Instance;
        }
        else if (Type = "Type2")
        {
            s = Strategy2<StrategyValidator>.Instance;
        }
        return s;
    }

    private StrategyBase s;
}

Here's the intended usage

Factory f = new Factory(); 

f.CreateStrategy<WMIValidator>("WMI");
f.CreateStrategy<ABCDValidator>("ABCD");

where WMIValidator and ABCDValidator are unrelated types, but the actual classes created by CreateStrategy function are related in a hierarchy e.g. having a common base StrategyBase

Here is a sample code to illustrate the issue

namespace TestCSharp
{
    public interface IStrategy
    {
    };

    public interface S1 : IStrategy
    {
        void f1();
        void f2();
    };

    public class S1Concrete : S1
    {
        public void f1() { }
        public void f2() { }
    }

    public interface S2 : IStrategy
    {
        void f3();
        void f4();
    };

    public class S2Concrete : S2
    {
        public void f3() { }
        public void f4() { }
    };

    public interface ProductBase
    {
    };

    class Product1<T> : ProductBase where T : S1
    {
    };

    class Product2<T> : ProductBase where T : S2
    {
    };

    public class Factory
    {
        public ProductBase Create<T>(String Type)
        {
            if (Type == "P1")
                return new Product1<T>();
            else if (Type == "P2")
                return new Product2<T>();
        }
    };

    class Program
    {
        static void Main(string[] args)
        {
            Factory f = new Factory();
            ProductBase s = f.Create<S1Concrete>("Type1");
        }
    }
}

The error I get is

The type 'T' cannot be used as type parameter 'T' in the generic type or method 'TestCSharp.Product1'. There is no boxing conversion or type parameter conversion from 'T' to 'TestCSharp.S1'.

Upvotes: 2

Views: 13737

Answers (4)

Jason Satterfield
Jason Satterfield

Reputation: 147

Have you considered overloading the Create<> function? I don't have VisualStudio handy right now, but would the following code work for your situation?

namespace ... {
    // ... other code here...

    public class Factory {
        public Product1<T> Create<T>() where T : S1 {
            return new Product1<T>();
        }
        public Product2<T> Create<T>() where T : S2 {
            return new Product2<T>();
        }
    }

    class Program {
        static void Main(string[] args) {
            Factory f = new Factory();
            ProductBase s = f.Create<S1Concrete>();
        }
    }
}

Additionally, you may wish to move your type constraints to a lower level. Consider writing an abstract base ProductBase class (that inherits from an IProductBase interface?) as follows:

class ProductBase<T> : IProductBase where T : IStrategy { }

This may help to alleviate some of your headaches.

Upvotes: 0

Tilak
Tilak

Reputation: 30698

You can change the function to take StrategyValidator as type.

From

public static StrategyBase CreateStrategy<StrategyValidator>(String Type)

To

public static StrategyBase CreateStrategy<T>(String Type) where T:StrategyValidator

To answer you question, You cannot avoid conditional checks.

To simplify the code can move the different combinations ("Type1", "Type2" , etc) to either dictionary or to the configuration if you use Dependency Injection, and then can you reflection.

Example.

    if (!dict.ContainsKey(key)) 
       throw New InvalidArgumentException();

    StrategyBase EnumImp = null;

    var instance = dict[key].MakeGenericType(typeOf(type)).GetProperty("Instance",  BindingFlags.Static | BindingFlags.Public ));   //dict is Dictionary<string, Type>

Upvotes: 0

e_ne
e_ne

Reputation: 8459

I think that the answer in this case is, it depends on what you want Product and Strategy to do. What you seem to be trying to do is splitting your logic in two branches. Then you want to couple it again by using generics, but as you can notice, it won't work.

Consider a scenario, similar to yours above -- But where each class implementing IStrategy has one instead of two methods which does side effect (i.e. print a string). You use generics when the range of types allowed have something in common. In the case I just mentioned, both have a method returning void and accepting no parameters; so we can add a method to IStrategy, for instance:

public interface IStrategy
{
    void ExecuteLogic();
};

public class S1 : IStrategy
{
    public void ExecuteLogic()
    {
        OneMethod();
    }

    void OneMethod()
    {
        Console.WriteLine("Hello");
    }
};

public class S2 : IStrategy
{
    public void ExecuteLogic()
    {
        TotallyDifferentMethod();
    }

    void TotallyDifferentMethod()
    {
        Console.WriteLine("World");
    }
};

Now, you also said that Strategy1 and Strategy2 have a different validation mechanism. However, it seems to me that you use them in the same method and context (and thus the same parameters and variables), so there must be something that makes them similar. Still, having defined IStrategy in the way we require, we can just use that as a constraint for Create<T>. So, Factory becomes:

public class Factory
{
    public ProductBase Create<T>(String Type) where T : IStrategy
    {
        if (Type == "P1")
            return new Product1<T>();
        else if (Type == "P2")
            return new Product2<T>();
        return null;
    }
};

But there's still one case. If you don't want Product1 to be called with S2 as a generic type, or Product2 to have S1 as its generic, then why using generics in the first place? You could easily couple the products with their relative strategies and also simplify the code remarkably.

In case I missed something (or the entire question) please leave a comment and I'll try to adapt my answer.

EDIT: since now you've redefined your example and used S1 and S2 as interfaces, I can see what you mean. A way would be defining multiple generic types and constraints for Factory.Create. Example:

public ProductBase Create<T1, T2>(String Type) where T1 : S1 where T2 : S2

It would be impossible otherwise, as you properly stated, because there's no common ancestor of S1 and S2 which can be accepted by your Product classes.

Upvotes: 1

Nathan Baulch
Nathan Baulch

Reputation: 20683

I don't really understand your scenario fully but as far as I can tell the factory pattern you're using would have to instantiate products using reflection. This is a little ugly because it doesn't give the consumer any hints about what strategy types can be used with a given product name.

public class Factory
{
    public ProductBase Create<T>(string name)
    {
        Type type;
        switch (name)
        {
            case "P1":
                type = typeof (Product1<>);
                break;
            case "P2":
                type = typeof (Product2<>);
                break;
            case "P3":
                type = typeof (Product3<>);
                break;
            default:
                return null;
        }
        type = type.MakeGenericType(typeof (T));
        return (ProductBase) Activator.CreateInstance(type);
    }
}

Upvotes: 2

Related Questions