0





23

Advertisement

Subject

This is simplified example of implementing a factory pattern in a way to restrict direct access to constructors and prevent illegal ways to create objects from other assemblies.

In other words I want to limit other developers to only be able to create their objects using this factory. (there are things must be done during object creation which is not specific to one object but related to all objects hence the reason)


Solution

I have this delegate which is used to create object instances inside my factory.

internal delegate T Instantiate<out T>(/*parameters*/) where T : Vehicle;

As you can see this delegate is internal, all constructors related to Vehicle and its sub-types are also internal.

internal interface IVehicle{/*...*/}
public class Vehicle
{
    //constructor is not protected. so this cant be intertied from outside assembly.
    internal Vehicle(/*parameters*/){ }
}
public class Car : Vehicle
{
    internal Car(/*parameters*/) : base(/*parameters*/) { }
}

Now here is the trick.

/// <summary>
/// Choose from one of predefined containers to create your vehicle from factoy.
/// </summary>
/// <typeparam name="T">type of vehicle</typeparam>
public class InstanceContainer<T> where T : Vehicle
{
    internal InstanceContainer(Instantiate<T> instance) // internal
    {
        GetInstance = instance;
    }

    // delegate which creates new instance of T
    internal Instantiate<T> GetInstance { get; } 
}

This makes others be able to choose their delegate but not access actual delegate. here are some options they can choose.

/// <summary>
/// Choose this to create car from factory.
/// </summary>
public static InstanceContainer<Car> CarInstance { get; } = new InstanceContainer<Car>(CreateCar);

private static readonly Instantiate<Car> CreateCar = (/*parameters*/) => new Car(/*parameters*/);

// ... others options like Plane, Ship etc

Example

Here is one of the methods to create an object from factory.

public static T CreateVehicle<T>(/*parameters,*/ InstanceContainer<T> instance) where T : Vehicle
{
    T obj;
    //...
    // At some point:
        obj = instance.GetInstance(/*parameters*/);
    //...
    return obj;
}

Now here is how to use it. (from other assemblies say create a Car)

VehicleFactory.CreateVehicle(/*parameters,*/ VehicleFactory.CarInstance);

This works fine, it complies fine and runs fine till now.

Note: VehicleFactory is public static class holding instances, methods, delegates...


Question

Now I want to make InstanceContainer co-variant so I can pass instances abstractly.

// turns long name into short one!
using VehicleInstance = Factories.VehicleFactory.InstanceContainer<Factories.Goods.Vehicle>

//...
//...

VehicleInstance instance;
// instance will be choosen based on some conditions. like this:
if(true)
    instance = VehicleFactory.CarInstance; // we got magic co-variant here!
else
    instance = VehicleFactory.PlaneInstance;

VehicleFactory.CreateVehicle(/*parameters,*/ instance);

Is there any way to make InstanceContainer co-variant but not expose its delegate to other assemblies?

This is what I have tried so far:

public interface IContainer<out T>
{
    // empty!
}

internal class InstanceContainer<T> : IContainer<T> where T : Vehicle
{
    internal InstanceContainer(Instance<T> instance)
    {
        GetInstance = instance;
    }

    internal Instance<T> GetInstance { get; }
}

This time IContainer is public interface and InstanceContainer is internal. that means we must use IContainer instead for safe access. little things must change:

// works fine!
public static IContainer<Car> CarInstance { get; } = new InstanceContainer<Car>(CreateCar);

public static T CreateVehicle<T>(/*parameters,*/ IContainer<T> instance)
{
    T obj;
    //...
    // At some point:
        obj = ((InstanceContainer<T>)instance).GetInstance(/*parameters*/); // runtime error.
    //...
    return obj;
}

Problem and Conclusion

The problem appears at runtime. we can not cast co-variant interface back to its actual class implementation.

Sorry for long question. but I know without subject, the answer for title would be pretty straight forward:

"You cant. because generic variants are pinned to interfaces. interfaces are pinned to public implements. it cant be internal. that's c# limit thing :("

I've also tried explicit implements but that does not work either. you basically end up with public interface and that will expose internal delegate anyway.

So is there better way to implement such factory pattern or am I using wrong design pattern? maybe I'm overthinking and solution is simple.

Thanks for giving your time and answer.

Question author M-kazem-akhgary | Source

Advertisement


0


I would simplify your solution to this:

internal delegate Vehicle VehicleConstructor(string data, string type);

public static class Factory {
    private static readonly Dictionary<Type, VehicleConstructor> _factories = new Dictionary<Type, VehicleConstructor>() {
        {typeof(Car), (data, type) => new Car(data)},
        {typeof(Plane), (data, type) => new Plane(data, type)}
    };

    public static T CreateVehicle<T>(string data, string type) where T : Vehicle {
        if (!_factories.ContainsKey(typeof(T)))
            throw new Exception("Cannot create Vehicle of type " + typeof(T).Name);
        return (T) _factories[typeof(T)](data, type);
    }

    public static Vehicle CreateVehicle(Type vehicleType, string data, string type) {
        if (!_factories.ContainsKey(vehicleType))
            throw new Exception("Cannot create Vehicle of type " + vehicleType.Name);
        return _factories[vehicleType](data, type);
    }
}

Then you can do both this:

var car = Factory.CreateVehicle<Car>("data", "type");

And this:

var v = Factory.CreateVehicle(true ? typeof(Car) : typeof(Plane), "data", "type");
Answer author Evk