为什么不能用派生类代替接口成员中的基类?

时间:2018-05-16 12:53:33

标签: c# oop

我无法创建可以使用派生类的接口实现。我一直认为这是可能的,我很困惑为什么我无法做到这一点。如果我将BaseType更改为抽象类也是不可能的。

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)'

为什么这不可能?我进入了一个现实世界的情况,这将是有用的。

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

这是我所遇到的现实世界问题的一个例子:

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

4 个答案:

答案 0 :(得分:1)

您可以使用泛型类型和逆变来解决这个问题。您的代码应如下所示:

    // 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建议 - &gt;用法:var a = new FirefoxWebDriverFactory();

答案 1 :(得分:1)

最终目标是能够为任何给定的配置获得正确的工厂。

没有泛型

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

您让所有工厂都实现了界面,然后您可以将它们全部存储在List<IFactory>中。只需使用给定的配置实例查询所有配置实例,然后询问它们是否可以实现。您也可以省略CanYouMakeIt()并让MakeIt返回null,如果这个工厂无法做到这一点。

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

使用泛型

注意:IFactory可以省略,但它可以方便地将它存储在更紧凑的列表中。

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

现在调用是棘手的部分。像Autofac这样的DI框架可以让这种方式更容易,但让我们看看我们将如何解决这个问题。

我在这里没有做任何错误检查......

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

我没有运行任何这些代码,所有这些都来自我的头脑,因为我只想说明如何使用反射。

使用后一种方法,您基本上可以根据动态调用代码交换强类型工厂定义。

答案 2 :(得分:1)

说明

  

为什么这不可能?我已经进入了一个有用的现实世界。

有用与否,这是不合逻辑的。您部分违反了界面规定的合同。

为了向您展示您的结论无效的原因,让我们看一下我们为实现目标而采取的单独步骤。

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

到目前为止没有任何异常。

我们说我有一家名叫Chez Flater的餐馆。我想聘请厨师,所以我就他希望我的厨师在他们工作时做的事情起草合同:

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

注意此接口定义的内容。把合同说成文字:

  

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

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

您现在应该知道为什么这个类违反了合同。它希望成为一个IChezFlaterChef,但它实际上并没有完成我对厨师所需的所有工作。 VeganChef只想烹饪Tofu。我不会雇用他。

你对此的反应可能是&#34;但我只有豆腐需要烹饪&#34;。这可能是正确的今天,但是如果我决定明天要把肉放回菜单上呢?

以下代码应始终有效:

IEnumerable<Food> myFood = GetSomeFoods();

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

    chef.Cook(food);
}

如果你按照你想要的方式实施你的VeganChef,这个代码可能会在意外的时间爆炸,当他是可用的厨师并且你要求他做的东西不是豆腐。

这是一个问题。我的餐厅不会有效率,因为我现在必须与一位拒绝工作的厨师打交道(=抛出异常),我将不得不寻找第二位厨师。 我特别要求我的所有厨师能够烹饪任何食物因为我不希望这种情况发生。要么我不应该期待我的厨师呢;或者我不应该聘请素食主厨。编译器指出我不应该雇用厨师:

  

&#39;实施&#39;没有实现接口成员&SomesInface.Method(BaseType)&#39;

相当于

  

[The VeganChef]没有预期[烹饪任何食物]。

换句话说,编译器阻止您打开餐厅,因为它知道一旦餐厅启动并运行,这个VeganChef将成为您的问题。

当你编写代码时,你无法知道在这个特定的餐厅会使用哪种厨师和食物,因为工作人员和菜单还没有确定(我们是还在开发餐厅本身)

如果我写这段代码:

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

这取决于每个员工保证具有Name属性的逻辑。

但是你的VeganChef无法烹饪每种食物。由于我要求我的IChezFlaterChef能够烹饪任何食物,这在逻辑上意味着VeganChef不能被雇用为IChezFlaterChef

解决方案

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

  

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

您想要的更准确地表达为:

  

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

作为餐馆老板,我改变了对餐厅厨师意味着什么的期望。我没有要求所有的厨师都能够烹饪我所有的食物,而是现在招聘只能烹饪一种特定食物的专家。

当一位厨师希望成为IChezFlaterChef时,明显的第一个问题就变成了#34;对于什么食物?&#34;。该信息由通用类型

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

注意where T : Food。这样可以确保您无法创建一个无意义的IChezFlaterChef<DateTime>

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

现在这个类是有效的,因为它符合IChezFlaterChef<TFood>定义的合同。

假设您想要一位可以烹饪两种食物的厨师。你可以做到这一点,但你必须实现两次接口(每种都有不同的食物类型):

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

答案 3 :(得分:0)

阅读问题

  

为什么不能用派生类代替接口成员中的基类?

我解释了为什么你不能这样做,而不是展示你如何以其他方式做到这一点。

根据您的合约界面interface SomeInterface 实现此接口的每个类都必须有一个方法Method(BaseType type),它可以接受每个类&#39;实现BaseType的对象。

意思是,实现BaseType的所有类的对象都可以在Method()中传递。

但是在派生类class Implementation : SomeInterface中,当尝试覆盖该方法时,这只能接受FirstDerivedClass类型的对象。

意思,

如果有人创建另一个类Demo来实现BaseType,现在按照界面的方法,这个类的对象也可以在Method(BaseType type)传递但是由于您的派生类Implementation确实只有接受FirstDerivedClass的方法,因此它不会接受Demo的对象。

仅在您的代码中有一个这样的类(与Demo相同),SecondDerivedClass

所以它违反了合同。

在你的派生类Implementation中,方法应该是这样的,它可以接受每一个类&#39;实现BaseType的对象。

如何做到这一点,只需接受BaseType方法中的Implementation类型实例。

public void Method(Base type) {}代替public void Method(FirstDerivedClass type) {}

在此方法的第一行中,您可以按Base将类型从FirstDerivedClass转换为FirstDerivedClass obj = (FirstDerivedClass)type

此行不是动态的,因为您知道编码时应该在哪个类型