【问题标题】:Abstract factory method with fixed type parameter具有固定类型参数的抽象工厂方法
【发布时间】:2016-10-06 16:56:08
【问题描述】:

有没有一种简洁的方法来指定一个类必须包含一个工厂方法,该方法返回与覆盖抽象方法的类相同的对象? (编辑: 或者正如 Johnathon Sullinger 更雄辩地说的那样,[...] 有一个基类强制子类实现一个返回子类本身实例的方法,并且不允许返回继承自基类的任何其他类型的实例。)

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

我希望编译时保证

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

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

    我认为这在 C# 中是不可能的。

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

    public abstract TFoo WithSomeProp<TFoo>(SomeProp prop)
        where TFoo : BaseFoo;
    

    这不会阻止FancyFoo.WithSomeProp 返回SimpleFoos,但是可以。

    这个抽象方法本身似乎可以工作,但是我的具体定义失败了:

    public override SimpleFoo WithSomeProp(SomeProp prop)
    {
        return new SimpleFoo(this.SomeOtherProp, ..., prop);
    }
    

    警告

    找不到合适的方法来覆盖

    在我看来,在抽象方法中指定类型参数不允许在这些定义的覆盖中修复它们,而是指定“应该存在具有类型参数的方法”。

现在我只有public abstract BaseFoo WithSomeProp(SomeProp prop);

【问题讨论】:

  • 我可以看一下这个的示例用法,所以我可以尝试了解您将如何使用这个 API。例如,如果允许使用静态摘要,您将如何使用它?这仅仅是创建和配置子实例的静态工厂方法吗?

标签: c# types


【解决方案1】:

这听起来像你想要做的,是让一个基类强制一个子类实现一个返回子类本身实例的方法,并且不允许返回从基类继承的任何其他类型的实例班级。不幸的是,据我所知,这不是你能做到的。

但是,您可以强制子类将其类型指定给基类,以便基类可以强制返回值必须是子类指定的类型。

例如,给定一个名为BaseFactoryBaseFactory&lt;T&gt; 的基类,我们可以创建一个抽象类,要求子类向父类指定创建方法返回的类型。我们包含一个 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&lt;T&gt; 将负责创建 TImpl 的新实例并将其返回。

public abstract class BaseFactory { }

public abstract class BaseFactory<TImpl> : BaseFactory where TImpl : BaseFactory
{
    public abstract TImpl WithSomeProp();
}

现在,可以创建您的子类,并从BaseFactory&lt;T&gt; 继承,告诉基类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();
    }
}

如果您真的想确保指定的泛型与拥有的 Type 相同,则可以改为将 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();
    }
}

现在,如果您创建的子类将不同的 Type 传递为 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();
    }
}

不确定这是否至少能得到你想要的,但我认为这可能是你能得到的最接近的东西。

【讨论】:

  • 非常感谢。你在课堂上传递类型的方法是鼓舞人心的。其他一些偏差是我的属性是只读的,并且我没有无参数构造函数(new() 需要)。
【解决方案2】:

受到 Johnathon Sullinger 的精彩回答的启发,这是我结束的代码。 (我添加了一个主题。)

我将类型参数T 与类定义一起传递并约束T : Base&lt;T&gt;

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-08
    • 1970-01-01
    • 2017-02-08
    • 1970-01-01
    • 1970-01-01
    • 2011-01-05
    • 2014-01-14
    • 2021-06-28
    相关资源
    最近更新 更多