1

Let's say I want to make a pie baking application. It should be able to create an apple pie as well as a strawberry pie. But I would like to have the option to add more different pie types later on. So I make an interface called IPie, like this:

interface IPie
{
    List<string> Ingredients { get; set; }
    IPieMaker Maker { get; set; }
}

interface IPieMaker
{
    Task MakePie();
}

I could then have an implementation of IPieMaker for each type of pie. E.g. for apple pie, like this:

public AppliePieMaker : IPieMake 
{
    public Task MakePie()
    {
        // ...do something with ingredients, specific to the apple pie
    }
}

The actual implementation is built into the specific pie. For instance, apple pie could look like this:

public class ApplePie : IPie
{
    List<string> Ingredients { get; set; } = new List<string> { "Apples", "Flour", "Eggs" };
    IPieMaker Maker { get; set; } = new ApplePieMaker();        
}

So no matter the type of the pie, I'm always sure it can be made. Like this:

var pies = new List<IPie> 
{
    new ApplePie(),
    new StrawBerryPie(),
};

foreach (var pie in pies)
{
    pie.Maker.MakePie();
}

Now here comes the actual question:

All of the above depends on the on the maker being a property on the pie class (the implementation of IPie). What if I would like a more functional apporach, where the PieMaker is outside the class? So that I call it like this:

// From here, the pies are simple dumb data structures
var pies = new List<IPie> 
{
    new ApplePie(),
    new StrawBerryPie(),
};

foreach (var pie in pies)
{
    var maker = new PieMaker(pie);
    maker.MakePie();
}

How do I make the PieMaker class handle the different types of pie? Obviously it could start with a giant switch statement and then route the calls to specific pie makers (ApplePieMaker, StrawberryPieMaker, etc.), but isn't that a bad practice? How would you do this in functional programming?

(I am aware that I am not doing actual functional programming, but I like the simplicity of it, which is why I am curious about a less object oriented approach).

Jakob Busk Sørensen
  • 5,599
  • 7
  • 44
  • 96

2 Answers2

3

The reason that switching on subtypes of an abstract type is considered a bad practice in most object oriented languages, is that inheritance is designed to be an open extensibility point where anyone can add new implementations of (in your case) IPie, including third parties consuming your code as a library.

In functional languages, you would usually represent the different kinds of pie as a sum type. It is not considered bad practice to pattern match on the cases of a sum type, as the type represents a closed hierarchy that can only be extended by modifying the type.

Unfortunately, C# does not yet contain a way to create a closed class hierarchy that can emulate a sum type, but if you're not actually distributing your code for consumption by third parties, it can be fine to use type-switching anyway. Just be aware that the compiler will not be able to warn you about "non-exhaustive pattern matches" in the same way as in most functional languages.

Jonas Høgh
  • 10,358
  • 1
  • 26
  • 46
2

One idea could be to have a specific PieMaker for each Pie, and bind the implementation classes to it's name, i.e. for a class ApplePie: IPie there must be a class named ApplePieMaker: IPiemaker<IPie>.

public interface IPie 
{
  List<string> Ingredients { get; set; }
}

public interface IPieMaker<in IPie>
{
  Make(IPie pie);
}

Then there is a mediator which gets the specific pie, looks for a SpecificPieMaker, creates one instance and invokes the Make method.

void Make(object pie)
{
  var makerType = Type.GetType($"{pie.GetType().FullName}Maker");
  var maker =  ActivatorUtilities.CreateInstance(ServiceProvider, makerType , new object[] {} );
  makerType.GetMethod("Make").Invoke( .....
}

Note: this code shows only some core ideas of this concept. You can find more information when looking for "mediator pattern" (I know it is not fully the same, but you can get some ideas from it).

Jakob Busk Sørensen
  • 5,599
  • 7
  • 44
  • 96
TWP
  • 250
  • 1
  • 5
  • 13
  • 1
    Great idea. In fact, Jimmy Bogard has a great library that does exactly this: https://github.com/jbogard/MediatR – Jonas Høgh May 11 '20 at 08:12
  • 1
    Wouldn't it have to be `var makerType = Type.GetType($"{pie,GetType().FullName}Maker");`? – Jakob Busk Sørensen May 11 '20 at 08:40
  • 1
    Nice solution! Just a little improvement: as all "makers" implements `IPieMaker` interface, the result of the activator could be casted to `IPieMaker` and then there is no need to use reflection to call the `Make` method, it could be called directly – CicheR May 11 '20 at 10:25
  • Yes you are right. This was because in my production code I also implemented a generic response type ```IRequest``` (in our case IPie). I found no other way of calling a generic interface method than using reflection. But maybe there is another solution? – TWP May 11 '20 at 10:40