在遵循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很慢。我应该以不同的方式解决这个问题吗?