具有固定类型参数的抽象工厂方法
本文关键字:抽象 工厂 方法 类型参数 | 更新日期: 2023-09-27 18:08:50
是否有一种简洁的方法来指定一个类必须包含一个工厂方法,该方法返回与覆盖抽象方法的类相同类型的对象?(编辑:或者正如Johnathon Sullinger更雄辩地指出的那样,[…])
让基类强制子类实现返回子类本身实例的方法,并且不允许返回继承自基类的任何其他类型的实例。)例如,如果我有两个类,SimpleFoo : BaseFoo
和FancyFoo : BaseFoo
,我可以定义一个抽象工厂方法public TFoo WithSomeProp(SomeProp prop)
,其中TFoo
是一个类型参数,以某种方式由抽象方法定义固定到覆盖它的特定类吗?
我希望编译时保证
在
SomeFoo : BaseFoo
中具体的WithSomeProp
方法定义将只能产生SomeFoo
s。如果静态抽象方法定义是合法的,也许下面的(伪语法)方法扩展最好地表达了这种需求:public static abstract TFoo WithSomeProp<TFoo>(this TFoo source, SomeProp prop) where TFoo : BaseFoo;
或者至少以某种方式在抽象方法中参数化返回类型,例如
public abstract TFoo WithSomeProp<TFoo>(SomeProp prop) where TFoo : BaseFoo;
这不会阻止
FancyFoo.WithSomeProp
返回SimpleFoo
s,但是ok。这个抽象方法本身似乎可以工作,但是我的具体定义就失败了:
带有警告的public override SimpleFoo WithSomeProp(SomeProp prop) { return new SimpleFoo(this.SomeOtherProp, ..., prop); }
没有找到合适的方法覆盖
在我看来,在抽象方法中指定类型参数不允许在这些定义的重写中修复它们,而是指定"具有类型参数的方法应该存在"。
现在我只有public abstract BaseFoo WithSomeProp(SomeProp prop);
。
这听起来像你想做的,是有一个基类强制子类实现一个方法,返回子类本身的一个实例,不允许返回任何其他类型的实例继承自基类。遗憾的是,据我所知,这不是你能做到的。
你可以强制子类指定它的类型给基类,这样基类就可以强制返回值必须是子类指定的类型。
例如,给定一个名为BaseFactory
和BaseFactory<T>
的基类,我们可以创建一个抽象类,它要求子类向父类指定创建方法返回的类型。我们包含一个BaseFactory
类,这样我们可以约束T
只作为BaseFactory
的子类。
编辑
我将把最初的答案留在下面,以防它有帮助,但经过一番思考,我想我有一个更好的解决方案。
您仍然需要基类接受一个泛型参数,该参数定义子类型是什么。然而,现在的不同之处在于基类有一个静态创建方法,而不是实例方法。您可以使用此创建方法来创建子类的新实例,并可选择在返回新实例之前调用回调来配置新实例的属性值。
public abstract class BaseFactory { }
public abstract class BaseFactory<TImpl> : BaseFactory where TImpl : BaseFactory, new()
{
public static TImpl Create(Action<TImpl> itemConfiguration = null)
{
var child = new TImpl();
itemConfiguration?.Invoke(child);
return child;
}
}
然后您只需正常创建您的子类,而不必担心重写任何方法。
public class Foo : BaseFactory<Foo>
{
public bool IsCompleted { get; set; }
public int Percentage { get; set; }
public string Data { get; set; }
}
public class Bar : BaseFactory<Bar>
{
public string Username { get; set; }
}
那么你将使用工厂。
class Program
{
static void Main(string[] args)
{
// Both work
Bar bar1 = Bar.Create();
Foo foo1 = Foo.Create();
// Won't compile because of different Types.
Bar bar2 = Foo.Create();
// Allows for configuring the properties
Bar bar3 = Bar.Create(instanceBar => instanceBar.Username = "Jane Done");
Foo foo2 = Foo.Create(instanceFoo =>
{
instanceFoo.IsCompleted = true;
instanceFoo.Percentage = 100;
instanceFoo.Data = "My work here is done.";
});
}
原始回答
BaseFactory<T>
将负责创建TImpl
的新实例并将其返回。
public abstract class BaseFactory { }
public abstract class BaseFactory<TImpl> : BaseFactory where TImpl : BaseFactory
{
public abstract TImpl WithSomeProp();
}
现在,可以创建您的子类,并从BaseFactory<T>
继承,告诉基类T
代表自己。这意味着子进程只能返回它自己。
public class Foo : BaseFactory<Foo>
{
public override Foo WithSomeProp()
{
return new Foo();
}
}
public class Bar : BaseFactory<Bar>
{
public override Bar WithSomeProp()
{
return new Bar();
}
}
那么你可以这样使用:
class Program
{
static void Main(string[] args)
{
var obj1 = new Bar();
// Works
Bar obj2 = obj1.WithSomeProp();
// Won't compile because obj1 returns Bar.
Foo obj3 = obj1.WithSomeProp();
}
}
如果您真的想要确保指定的泛型与拥有的类型相同,您可以将WithSomeProp
改为受保护的方法,以便子类只能看到它。然后,在基类上创建一个可以执行类型检查的公共方法。
public abstract class BaseFactory { }
public abstract class BaseFactory<TImpl> : BaseFactory where TImpl : BaseFactory
{
protected abstract TImpl WithSomeProp();
public TImpl Create()
{
Type myType = this.GetType();
if (typeof(TImpl) != myType)
{
throw new InvalidOperationException($"{myType.Name} can not create instances of itself because the generic argument it provided to the factory is of a different Type.");
}
return this.WithSomeProp();
}
}
public class Foo : BaseFactory<Foo>
{
protected override Foo WithSomeProp()
{
return new Foo();
}
}
public class Bar : BaseFactory<Bar>
{
protected override Bar WithSomeProp()
{
return new Bar();
}
}
class Program
{
static void Main(string[] args)
{
var obj1 = new Bar();
// Works
Bar obj2 = obj1.Create();
// Won't compile because obj1 returns Bar.
Foo obj3 = obj1.Create();
}
}
现在,如果您创建一个子类,将不同的类型作为T
传递,基类将捕获它并抛出异常。
// Throws exception when BaseFactory.Create() is called, even though this compiles fine.
public class Bar : BaseFactory<Foo>
{
protected override Foo WithSomeProp()
{
return new Foo();
}
}
不确定这是否能让你至少得到你想要的,但我认为这可能是你能得到的最接近的东西。
受Johnathon Sullinger精彩回答的启发,我以以下代码结尾。(我添加了一个主题。)
我将类型参数T
与类定义一起传递,并约束T : Base<T>
.
-
BaseHyperLink.cs:
public abstract class BaseHyperLink<THyperLink> : Entity<int> where THyperLink : BaseHyperLink<THyperLink> { protected BaseHyperLink(int? id, Uri hyperLink, ContentType contentType, DocumentType documentType) : base(id) { this.HyperLink = hyperLink; this.ContentType = contentType; this.DocumentType = documentType; } public Uri HyperLink { get; } public ContentType ContentType { get; } public DocumentType DocumentType { get; } public abstract THyperLink WithContentType(ContentType contentType); }
-
SharedHyperLink.cs:
public sealed class SharedHyperLink : BaseHyperLink<SharedHyperLink> { public SharedHyperLink(int? id, Uri hyperLink, ContentType contentType, DocumentType documentType) : base(id, hyperLink, contentType, documentType) { } public override SharedHyperLink WithContentType(ContentType contentType) { return new SharedHyperLink(this.Id, contentType, this.DocumentType); } }
-
MarkedHyperLink.cs:
public sealed class MarkedHyperLink : BaseHyperLink<MarkedHyperLink> { public MarkedHyperLink(int? id, Uri hyperLink, ContentType contentType, DocumentType documentType, Mark mark) : base(id, hyperLink, contentType, documentType) { this.Mark = mark; } public Mark Mark { get; } public override MarkedHyperLink WithContentType(ContentType contentType) { return new MarkedHyperLink(this.Id, contentType, this.DocumentType, this.Mark); } }