为什么不能用派生类代替接口成员中的基类? [英] Why can't derived classes be substituted in place of base classes in interface members?

查看:47
本文介绍了为什么不能用派生类代替接口成员中的基类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我无法创建可以与派生类一起使用的接口的实现.我一直以为这是可能的,而我为何无法做到这一点感到困惑.如果我也将BaseType更改为抽象类,也是不可能的.

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.

例如,Selenium WebDriver有许多浏览器驱动程序.有一个抽象的DriverOptions类,带有派生类(ChromeOptionsFirefoxOptions等).

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

推荐答案

说明

为什么这不可能?我进入了现实世界,这将很有用.

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.

假设我有一家名为Chez Flater的餐厅.我想聘请厨师,所以我起草了一份我希望厨师工作的合同:

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:

如果您想成为IChezFlaterChef,您必须能够烹饪任何食物.

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

您现在应该看到此类违反合同的原因.它想成为IChezFlaterChef,但实际上并没有完成我厨师需要的所有工作. VeganChef只想煮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?

以下代码应始终有效:

IEnumerable<Food> myFood = GetSomeFoods();

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

    chef.Cook(food);
}

如果您以自己想要的方式实现VeganChef,那么当他是厨师时,并且您要他烹饪不是豆腐的东西时,此代码可能会在意外的时间炸毁.

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:

实施"未实现接口成员"SomeInterface.Method(BaseType)"

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

等同于

[The ChefChef]没有完成预期的[烹饪任何食物].

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

换句话说,编译器阻止您打开餐厅,因为它知道一旦餐厅启动并运营,此VeganChef对您来说将是一个问题.

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)

如果我编写这段代码:

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

这取决于每个员工保证拥有姓名属性的逻辑.

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

但是您的VeganChef不能烹饪所有食物.由于我要求我的IChezFlaterChef能够烹饪任何食物,因此从逻辑上讲,这意味着VeganChef不能被聘为IChezFlaterChef.

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.

您当前的IChezFlaterChef合同根本不正确:

Your current IChezFlaterChef contract is simply not correct:

如果您想成为IChezFlaterChef,您必须能够烹饪任何食物.

您想要的内容更正确地表示为:

What you want is more correctly expressed as:

如果您想成为特定食物类型的IChezFlaterChef ,您必须能够烹饪该特定食物类型.

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.

当厨师想成为IChezFlaterChef时,显而易见的第一个问题变成要吃什么?".该信息由通用类型提供:

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

通知where T : Food.这样可以确保您不能执行诸如创建没有意义的IChezFlaterChef<DateTime>之类的事情.

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) {}
}

现在该类有效,因为它满足了IChezFlaterChef<TFood>定义的约定.

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) {}
}

这篇关于为什么不能用派生类代替接口成员中的基类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆