user9993
user9993

Reputation: 6170

Why can't derived classes be substituted in place of base classes in interface members?

I'm unable to create an implementation of an interface that can work with a derived class. I have always thought this was possible, and I'm confused as to why I am unable to do this. It also is not possible if I change BaseType to an abstract class either.

public interface BaseType {}

public class FirstDerivedClass : BaseType {}

public class SecondDerivedClass : BaseType {}


public interface SomeInterface
{
    void Method(BaseType type);
}

public class Implementation : SomeInterface
{
    public void Method(FirstDerivedClass type) {}
}

'Implementation' does not implement interface member 'SomeInterface.Method(BaseType)'

Why is this not possible? I've come into a real world situation where this would be useful.

For example, Selenium WebDriver has a number of browser drivers. There is an abstract DriverOptions class with derived classes (ChromeOptions, FirefoxOptions, etc.).

This is an example of the real world problem I've got:

// Third party code - Selenium.
public abstract class DriverOptions {}
public class ChromeOptions : DriverOptions {}
public class FirefoxOptions : DriverOptions {}


// My code
public interface IWebDriverFactory
{
    IWebDriver GetWebDriver(DriverOptions options);
}

public class ChromeWebDriverFactory : IWebDriverFactory
{
    public IWebDriver GetWebDriver(ChromeOptions options)
    {
        // implementation details for initalising Chrome and use options from ChromeOptions
    }
}

public class FirefoxWebDriverFactory : IWebDriverFactory
{
    public IWebDriver GetWebDriver(FirefoxOptions options)
    {
        // implementation details for initalising Firefox and use options from FirefoxOptions
    }
}

Upvotes: 4

Views: 162

Answers (4)

Flater
Flater

Reputation: 13773

Explanation

Why is this not possible? I've come into a real world situation where this would be useful.

Useful or not, it is illogical. You are partially violating the contract that the interface stipulates.

To show you why your conclusion is invalid, let's look at the separate steps we took to get there.

public class Food {}
public class Meat :  Food {}
public class Poultry : Food {}
public class Tofu :  Food {}

Nothing out of the ordinary so far.

Let's say I have a restaurant called Chez Flater. I want to hire chefs, so I draft a contract for what I expect my chefs to do when they're working:

public interface IChezFlaterChef
{
    void Cook(Food food);
}

Notice what this interface defines. To put the contract into words:

If you want to be an IChezFlaterChef, you must be able to cook anything that is Food.

public class VeganChef : IChezFlaterChef
{
    public void Cook(Tofu tofu) {}
}

You should now see why this class violates the contract. It wants to be an IChezFlaterChef, but it's not actually doing all the jobs that I require of my chefs. The VeganChef only wants to cook Tofu. I would not hire him.

Your response to this might be "but I only have tofu that needs cooking". This may be correct today, but what if I decide that I want to put meat back on the menu tomorrow?

The following code is supposed to always work:

IEnumerable<Food> myFood = GetSomeFoods();

foreach(var food in myFoods)
{
    var availableChef = FindAvailableChef();

    chef.Cook(food);
}

If you implement your VeganChef the way you want to, this code can blow up at an unexpected time, when he's the available chef and you're asking him to cook something that's not Tofu.

That's a problem. My restaurant is not going to be run efficiently, because I now have to deal with a chef that refuses to work (= throws exception) and I'm going to have to look for a second chef.
I specifically required all of my chefs to be able to cook any food because I did not want this to happen. Either I should not expect this of my cooks; or I should not have hired the vegan chef. The compiler is pointing out that I should not be hiring the chef:

'Implementation' does not implement interface member 'SomeInterface.Method(BaseType)'

Is equivalent to

[The VeganChef] does not do the expected [cooking of any food].

In other words, the compiler is preventing you from opening your restaurant because it knows that this VeganChef is going to be a problem for you once the restaurant is up and running.

When you're writing code, you have no way of knowing which exact chefs and foods will be used in this particular restaurant, because the staff and menu hasn't been decided yet (we're still developing the restaurant itself)

If I write this code:

public string GetEmployeeName(Employee e)
{
    return e.Name;
}

This relies on the logic that every Employee is guaranteed to have a Name property.

But your VeganChef cannot cook every food. Since I require my IChezFlaterChefs to be able to cook any food, that logically means that VeganChef cannot be hired as an IChezFlaterChef.


Solution

Your current IChezFlaterChef contract is simply not correct:

If you want to be an IChezFlaterChef, you must be able to cook anything that is Food.

What you want is more correctly expressed as:

If you want to be an IChezFlaterChef for a particular food type, you must be able to cook that particular food type.

As the restaurant owner, I've changed my expectations of what it means to be a chef in my restaurant. Instead of requiring all my chefs to be able to cook all my food, I'm now open to hiring specialists who can only cook one particular food.

When a chef wants to be an IChezFlaterChef, the obvious first question then becomes "for what food?". That information is provided by a generic type:

public interface IChezFlaterChef<TFood> where T : Food
{
    void Cook(TFood food);
}

Notice where T : Food. This ensure that you can't do things like creating an IChezFlaterChef<DateTime> which makes no sense.

public class VeganChef : IChezFlaterChef<Tofu>
{
    public void Cook(Tofu tofu) {}
}

And now this class is valid, because it fulfills the contract that IChezFlaterChef<TFood> has defined.

Suppose you want a chef that can cook two foods. You can do this, but you must implement the interface twice (each with a different food type):

public class MeatAndPoultryChef : IChezFlaterChef<Meat>, IChezFlaterChef<Poultry>
{
    //Needed for IChezFlaterChef<Meat>
    public void Cook(Meat meat) {}

    //Needed for IChezFlaterChef<Poultry>
    public void Cook(Poultry poultry) {}
}

Upvotes: 1

Amit
Amit

Reputation: 1857

Reading question

Why can't derived classes be substituted in place of base classes in interface members?

I'm explaining why you cannot do that, instead of showing how you can do that in other ways.

As per your contract interface interface SomeInterface Every class which implements this interface must have a method Method(BaseType type) which can accept every those classes' object which implements BaseType.

meaning, object of all those classes which implements BaseType can be passed in Method().

but in your derived class class Implementation : SomeInterface , when have tried to override that method, this can accept only FirstDerivedClass type's object.

meaning,

if someone creates another class say Demo which implements BaseType, now as per interface's method, this class's object too can be passed in Method(BaseType type) but as your derived class Implementation does have method which only accept FirstDerivedClass only, it will not accept Demo's object.

one such class (same as Demo) is already there in your code only, SecondDerivedClass

So it violates the contract.

In your derived class Implementation too, method should be such which can accept every those classes' object which implements BaseType.

Way to do it, simply accept BaseType Typed instance in method of Implementation.

public void Method(Base type) {} instead of public void Method(FirstDerivedClass type) {}

Here in first line of this method you can cast type from Base to FirstDerivedClass by FirstDerivedClass obj = (FirstDerivedClass)type.

This line isn't required to be dynamic, as you know which type should be here at coding time

Upvotes: 0

Dennis Kuypers
Dennis Kuypers

Reputation: 556

The ultimate goal is to be able to get the correct factory for any given configuration.

Without generics

interface IFactory
{
    bool CanYouMakeIt(IConfig config);
    IMadeThis MakeIt(IConfig config);
}

You let all factories implement the interface and then you can all store them in a List<IFactory>. Just query all of them with the given config instance and ask them if they can make it. You could also leave out CanYouMakeIt() and let MakeIt just return null if this is not possible for this factory.

class SampleFactory : IFactory
{
    public bool CanYouMakeIt(IConfig config) => config.GetType() == typeof(SampleConfig);
    public IMadeThis MakeIt(IConfig config) => new SampleMadeThis(config);
}

Using Generics

Note: IFactory can be omitted, but it makes it handy to store it in a more tightly typed list.

interface IFactory {}
interface IFactory<T> :IFactory where T : IConfig
{
    IMadeThis MakeIt(T config);
}

class SampleFactory : IFactory<SampleConfig>
{
    public IMadeThis MakeIt(SampleConfig config) => new SampleMadeThis(config);
}

Now invocation is the tricky part. A DI framework like Autofac can make this way easier, but lets see how we would go about this.

I'm not doing any error checking here...

class TheUltimateFactory
{
    private readonly IDictionary<Type,IFactory> _factories;

    public TheUltimateFactory(IFactory[] factories)
    {
        _factories = factories.ToDictionary(f => GetFactoryType(f.GetType()), f => f);
    }

    private Type GetFactoryType(Type type)
    {
        var genericInterface = type.GetInterfaces().First(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IFactory<>));
        return genericInterface.GetGenericArguments()[0];
    }

    public IMadeThis MakeIt(IConfig config)
    {
        var configType = config.GetType();
        var method = typeof(IFactory<>).MakeGenericType(configType).GetMethod("MakeIt");
        return (IMadeThis)method.Invoke(_factories[configType], new object[]{config});
    }
}

I did not run any of this code, all from the top of my head as I just want to illustrate how you can use reflection for this.

Using the latter approach you basically trade in a strongly typed factory definition against a dynamic invocation code.

Upvotes: 1

Dragos Stoica
Dragos Stoica

Reputation: 1935

You can solve that using generic types and contravariance. Your code should look like below:

    // Third party code - Selenium.
public abstract class DriverOptions { }
public class ChromeOptions : DriverOptions { }
public class FirefoxOptions : DriverOptions { }


// My code
public interface IWebDriverFactory<in T> where T:DriverOptions
{
    IWebDriver GetWebDriver(T options);
}

public class ChromeWebDriverFactory : IWebDriverFactory<ChromeOptions>
{
    public IWebDriver GetWebDriver(ChromeOptions options)
    {
        // implementation details for initalising Chrome and use options from ChromeOptions
    }
}

public class FirefoxWebDriverFactory : IWebDriverFactory<FirefoxOptions>
{
    public IWebDriver GetWebDriver(FirefoxOptions options)
    {
        // implementation details for initalising Firefox and use options from FirefoxOptions
    }
}

as @TSmith suggested -> usage: var a = new FirefoxWebDriverFactory();

Upvotes: 1

Related Questions