适用于大量对象的正确运行时构造模式

时间:2016-12-22 17:09:46

标签: c# design-patterns dependency-injection

在遵循LSP并始终保持对象处于有效状态的同时,在运行时创建派生对象的最佳方法是什么。

我对建筑模式比较新,比如Factory和Builder,我发现的大多数例子都非常简单。这是我的情景:

我有一个基类(为简洁起见,遗漏了一些东西):

public abstract BaseClass
{
    public string Property1 { get; set ... null guard; }
    public string Property2 { get; set ... conditional null guard; }

    public virtual bool RequiresProperty2 => false;

    protected BaseClass(string property1)
    {
        null guard
        Property1 = property1;
    }
}

我有50多个派生类。其中一些需要prop2,其中一些不需要。那些需要prop2的构造函数有一个强制prop2传入的构造函数,强制所有BaseClass派生对象在构造时都处于有效状态。我也试图坚持LSP,我正在使用Castle Windsor进行依赖注入。

我提出的解决方案是创建一个返回构建器的工厂:

public interface IFactory
{
    IBuilder Create(string type);
}

public interface IBuilder
{
    IBuilder SetProperty1(string property);
    IBuilder SetProperty2(string property);
    BaseClass Build();
}

工厂的具体实现通过反射加载从BaseClass继承的所有类型。当您调用Factory.Create(...)时,您传递一个字符串,该字符串是您要创建的类型的字符串名称。然后工厂创建一个构建器,将适当的Type传递给构建器的构造函数。建造者看起来像这样:

public sealed class ConcreteBuilder : IBuilder
{
    private static Type ClassType = typeof(BaseClass);

    private static readonly ConcurrentDictionary<Type, Delegate> 
        ClassConstructors = new ConcurrentDictionary<Type, Delegate>();

    private readonly Type type;

    private string property1;
    private string property2;

    public ConcreteBuilder(Type type)
    {
        if (type == null) throw new ArgumentNullException(nameof(type));
        if (!type.IsSubclassOf(ClassType))
        {
            throw new ArgumentException("Must derive from BaseClass.");
        }

        this.type = type;
    }

    public IBuilder SetProperty1(string property)
    {
        this.property1 = property;
        return this;
    }

    public IBuilder SetProperty2(string property)
    {
        this.property2 = property;
        return this;
    }

    public BaseClass Build()
    {
        var arguments = BuildArguments();

        Delegate ctor;
        if (ClassConstructors.TryGetValue(this.type, out ctor))
        {
            return (BaseClass)ctor.DynamicInvoke(arguments);
        }

        return (BaseClass)GetConstructor(arguments).DynamicInvoke(arguments);
    }

    private object[] BuildArguments()
    {
        var args = new List<object>();

        if (!string.IsNullOrEmpty(this.property1))
        {
            args.Add(this.property1);
        }

        if (!string.IsNullOrEmpty(this.property2))
        {
            args.Add(this.property2);
        }

        return args.ToArray();
    }

    private Delegate GetConstructor(object[] arguments)
    {
        var constructors = this.type.GetConstructors();
        foreach (var constructor in constructors)
        {
            var parameters = constructor.GetParameters();
            var parameterTypes = parameters.Select(p => p.ParameterType).ToArray();
            if (parameterTypes.Length != arguments.Length + 1) continue;

            if (!parameterTypes.Zip(arguments, TestArgumentForParameter).All(x => x))
            {
                continue;
            }

            var parameterExpressions = parameters.Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();
            var callConstructor = Expression.New(constructor, parameterExpressions);
            var ctor = Expression.Lambda(callConstructor, parameterExpressions).Compile();

            ClassConstructors.TryAdd(this.type, ctor);
            return ctor;
        }

        throw new MissingMethodException("No constructor found");
    }

    private static bool TestArgumentForParameter(Type parameterType, object argument)
    {
        return (argument == null && !parameterType.IsValueType) || (parameterType.IsInstanceOfType(argument));
    }
}

有更好的方法吗?我是以正确的方式来做这件事的吗?我知道DynamicInvoke很慢。我应该以不同的方式解决这个问题吗?

0 个答案:

没有答案