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

时间:2016-09-26 19:53:39

标签: c# types

是否有一种巧妙的方法来指定一个类必须包含一个工厂方法,该方法返回与覆盖抽象方法的类相同类型的对象? (编辑:或者正如Johnathon Sullinger更有说服力地说, [...]有一个基类强制执行子类来实现一个返回子类本身实例的方法,并且不允许返回从基类继承的任何其他Type的实例。

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

我希望编译时保证

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

    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返回SimpleFoo,但确定。

    这个抽象方法本身似乎有效,但我的具体定义失败了:

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

    带警告

      

    找不到合适的方法来覆盖

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

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

2 个答案:

答案 0 :(得分:4)

这听起来像你想要做的,有一个基类强制执行一个子类来实现一个返回子类本身实例的方法,并且不允许返回从base继承的任何其他Type的实例类。不幸的是,据我所知,这不是你能做的事情。

但是,您可以强制子类指定它对基类的类型,以便基类可以强制返回值必须是子类指定的类型。 / p>

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

如果你真的想确保指定的泛型与拥有的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();
    }
}

现在,如果您创建一个将类型传递为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();
    }
}

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

答案 1 :(得分:1)

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