我有类
Base
,以及扩展该类的类 A
、B
、C
、D
...。 Base
有一个带有太多参数的构造函数(不好的做法,但我无法改变它)。大多数子类仅具有这些参数,但某些子类可以具有 1 或 2 个以上参数。像这样的东西:
class Base {
private readonly string _arg1;
private readonly string _arg2;
(...)
public Base(string arg1, string arg2, string arg3, ..., string arg_n) {
_arg1 = arg1
_arg2 = arg2
(...)
}
}
class A : Base {
public A(string arg1, string arg2, string arg3, ..., string arg_n) :
base(arg1, arg2, arg3, ..., arg_n)
}
}
class B : Base {
private readonly string _arg_m;
public B(string arg1, string arg2, string arg3, ..., string arg_n, string arg_m) :
base(arg1, arg2, arg3, ..., arg_n)
_arg_m = arg_m;
}
}
etc...
我想实例化每个类的一个元素,使用相同的实例作为参数。比如:
var arg1 = "something";
(...)
var arg_n = "something";
var a = new A(arg1, arg2, arg3, ... , arg_n);
var b = new B(arg1, arg2, arg3, ... , arg_n, "foo");
(...)
我想在不重复太多代码的情况下做到这一点。我想我可以创建一个工厂:
class Factory {
Factory(string arg1, string arg2, string arg3, ..., string arg_n) {
(...)
}
然后重用这些参数。我的原始代码将简化为
var arg1 = "something";
(...)
var arg_n = "something";
var factory = new Factory(arg1, arg2, arg3, ... , arg_n);
var a = factory.GetA();
var b = factory.GetB("foo");
(...)
但是工厂将为所有孩子提供所有
new A(list of arguemnts)
。所以,基本上,我不是在减少代码,而是在移动它。
是否有一种模式可以无需如此冗长地创建所有这些子项?谢谢。
您可以创建一个包含所有参数的类:
class BaseArgs
{
public string Arg1 {get;}
public string Arg2 {get;}
public BaseArgs(string arg1, arg2)
{
Arg1 = arg1;
Arg2 = arg2;
}
}
将此对象传递给您的派生类:
class A : Base
{
public A(BaseArgs args) : base(args.Arg1, args.Arg2)
{
}
}
class B : Base
{
private string additional;
public B(BaseArgs args, string additional) : base(args.Arg1, args.Arg2)
{
this.additional = additional;
}
}
您可以创建一次
BaseArgs
对象并将其传递给派生类的所有实例:
var baseArgs = new BaseArgs("arg1","arg2");
var a = new A(baseArgs);
var b = new B(baseArgs, "foo");
如果将 setter 添加到
BaseArgs
的属性中,您甚至可以在创建实例之间仅修改一些参数:
var baseArgs = new BaseArgs("arg1","arg2");
var a = new A(baseArgs);
baseArgs.Arg1 = "changed";
var b = new B(baseArgs, "foo");
构建器允许您在构建对象之前收集参数。以下是构建器如何工作的示例:
class Base
{
public Base(int a, int b, int c)
{
}
}
class FirstImplementation : Base
{
public FirstImplementation(int a, int b, int c) : base(a, b, c)
{
}
}
class Builder
{
int a;
int b;
int c;
public Builder SetA(int a)
{
this.a = a;
return this;
}
public Builder SetB(int b)
{
this.b = b;
return this;
}
public Builder SetC(int c)
{
this.c = c;
return this;
}
public FirstImplementation Build()
{
// Add your null checks here!
return new FirstImplementation(a, b, c);
}
}
如您所见,使用构建器,您仍然需要每次提供所有参数。这就是导演发挥作用的地方。导演是可选的,但可以让你的问题消失。
class Director
{
public Base BuildMinimalViableProduct()
{
return new Builder()
.SetA(1)
.Build();
}
public Base BuildFullFeaturedProduct()
{
return new Builder()
.SetA(1)
.SetB(2)
.SetC(3)
.Build();
}
}
本页解释了构建器背后的想法:
一种替代方法是使用依赖注入容器。这允许您在容器中注册所有依赖项,并让容器弄清楚如何构造每个对象。典型用法可能类似于
var collection = new ServiceCollection()
collection.AddSingleton(myDependency1);
collection.AddSingleton(myDependency2);
collection.AddTransient<A>();
collection.AddSingleton<B>();
...
provider = collection.BuildServiceProvider();
var a= provider.GetService<A>();
var b= provider.GetService<B>();
如果参数具有不同类型,这往往效果最好。如果所有参数都是字符串,则容器无法确定哪个参数在哪里。但如果你的所有参数都是字符串,你可能还会遇到其他问题。
如果参数以某种方式相关,则将它们收集到可以在容器中注册并用作依赖项的参数对象中可能会很有用。但我会警告不要将任意不相关的参数放入这样的参数类中,因为这可能会导致比它解决的问题更多的问题。
另请注意,在项目开始时实现并用于构造大多数类型时,使用依赖项注入容器效果最佳。重新构建现有应用程序可能很困难。
您可以使用一个构建器,该构建器仅使用变量数组关键字
Base
作为参数动态实例化从 params
继承的类型。这个解决方案应该允许您构建任何类型,只要继承类型都正确传递 Base
所需的参数即可。
public class BaseBuilder
{
public static T Build<T>(params string[] args) where T : Base
{
var ctor = typeof(T).GetConstructors().First();
var parameters = ctor.GetParameters();
var arguments = new List<string>();
//Check args here
if (args == null || args.Length < parameters.Length)
throw new ArgumentException($"Invalid number of arguments, was expecting {parameters.Length}");
for (var i = 0; i < parameters.Length; i++)
{
arguments.Add(args[i]);
}
return (T)ctor.Invoke(arguments.ToArray());
}
}
然后,您可以在单个数组中拥有一组参数,只要顺序一致,就只会使用实际需要的参数。
var args = new[] { "a", "b", "c", "d", "e" };
var a = BaseBuilder.Build<A>(args);
var b = BaseBuilder.Build<B>(args);