sshine
sshine

Reputation: 16105

Abstract factory method with fixed type parameter

Is there a neat way to specify that a class must contain a factory method that returns the same kind of object as the class that overrides the abstract method? (Edit: Or as Johnathon Sullinger more eloquently puts it, [...] have a base class enforce a child class to implement a method that returns an instance of the child class itself, and not allow returning an instance of any other Type that inherits from the base class.)

For example, if I've got two classes, SimpleFoo : BaseFoo and FancyFoo : BaseFoo, can I define an abstract factory method public TFoo WithSomeProp(SomeProp prop) where TFoo is a type parameter that is somehow fixed by the abstract method definition to the particular class that overrides it?

I had hopes of compile-time guarantees that either

  1. a concrete WithSomeProp method definition in SomeFoo : BaseFoo will only be able to produce SomeFoos. If static abstract method definitions were legal, perhaps the following (pseudo-syntax) method extension best expresses this need:

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

    I don't think this is possible in C#.

  2. or at least some way to parameterize the return type in an abstract method, e.g.

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

    This wouldn't prevent FancyFoo.WithSomeProp from returning SimpleFoos, but ok.

    This abstract method itself seems to work, but my concrete definition then fails:

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

    with the warning

    no suitable method found to override

    It appears to me that specifying type parameters in an abstract method does not allow fixing them in the overrides of those definitions, but rather it specifies that "A method with a type parameter should exist".

For now I simply have public abstract BaseFoo WithSomeProp(SomeProp prop);.

Upvotes: 1

Views: 278

Answers (2)

sshine
sshine

Reputation: 16105

Inspired by Johnathon Sullinger's fine answer, here is the code I ended with. (I added a theme.)

I passed the type parameter T along with the class definition and constrained that 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);
        }
    }
    

Upvotes: 1

Johnathon Sullinger
Johnathon Sullinger

Reputation: 7414

It sounds like what you want to do, is have a base class enforce a child class to implement a method that returns an instance of the child class itself, and not allow returning an instance of any other Type that inherits from the base class. Unfortunately, to the best of my knowledge, that is not something you can do.

You can however force the child-class to specify what it's Type is to the base class, so that the base class can then enforce that the return value must be the Type specified by the child-class.

For instance, given a base class called BaseFactory, and BaseFactory<T>, we can create an abstract class that requires children to specify to the parent, what type the creation method returns. We include a BaseFactory class so we can constrain T to only being children classes of BaseFactory.

EDIT

I'll leave the original answer below in the event that it helps, but after some thought, I think I've got a better solution for you.

You'll still need the base class to take a generic argument that defines what the child Type is. The difference now however is that the base class has a static creation method instead of instance methods. You can use this creation method to create a new instance of the child class, and optionally invoke a callback for configuring the property values on the new instance before you return it.

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

You then just create your children classes normally, without worrying about overriding any methods.

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

Then you would use the factory as-such.

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.";
        });
    }

Original Answer

The BaseFactory<T> will be reponsible for creating a new instance of TImpl and giving it back.

public abstract class BaseFactory { }

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

Now, your child class can be created, and inherit from BaseFactory<T>, telling the base class that T represents itself. This means the child can only ever return itself.

public class Foo : BaseFactory<Foo>
{
    public override Foo WithSomeProp()
    {
        return new Foo();
    }
}

public class Bar : BaseFactory<Bar>
{
    public override Bar WithSomeProp()
    {
        return new Bar();
    }
}

Then you would use it like:

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

If you really want to make sure that the generic specified is the same as the owning Type, you could instead make WithSomeProp a protected method, so that children classes can only see it. Then, you create a public method on the base class that can do type checking.

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

Now, if you create a child class that passes a different Type as T, the base class will catch it and throw an exception.

// Throws exception when BaseFactory.Create() is called, even though this compiles fine.
public class Bar : BaseFactory<Foo>
{
    protected override Foo WithSomeProp()
    {
        return new Foo();
    }
}

Not sure if this gets you what you wanted at least, but I think this will probably be the closest thing you can get.

Upvotes: 4

Related Questions