具有固定类型参数的抽象工厂方法

本文关键字:抽象 工厂 方法 类型参数 | 更新日期: 2023-09-27 18:08:50

是否有一种简洁的方法来指定一个类必须包含一个工厂方法,该方法返回与覆盖抽象方法的类相同类型的对象?(编辑:或者正如Johnathon Sullinger更雄辩地指出的那样,[…])

让基类强制子类实现返回子类本身实例的方法,并且不允许返回继承自基类的任何其他类型的实例。)

例如,如果我有两个类,SimpleFoo : BaseFooFancyFoo : BaseFoo,我可以定义一个抽象工厂方法public TFoo WithSomeProp(SomeProp prop),其中TFoo是一个类型参数,以某种方式由抽象方法定义固定到覆盖它的特定类吗?

我希望编译时保证

  1. SomeFoo : BaseFoo中具体的WithSomeProp方法定义将只能产生SomeFoo s。如果静态抽象方法定义是合法的,也许下面的(伪语法)方法扩展最好地表达了这种需求:

    public static abstract TFoo WithSomeProp<TFoo>(this TFoo source, SomeProp prop)
        where TFoo : BaseFoo;
    

  2. 或者至少以某种方式在抽象方法中参数化返回类型,例如

    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);

具有固定类型参数的抽象工厂方法

这听起来像你想做的,是有一个基类强制子类实现一个方法,返回子类本身的一个实例,不允许返回任何其他类型的实例继承自基类。遗憾的是,据我所知,这不是你能做到的。

你可以强制子类指定它的类型给基类,这样基类就可以强制返回值必须是子类指定的类型。

例如,给定一个名为BaseFactoryBaseFactory<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);
        }
    }