【问题标题】:Using a public method of derived class that is not in interface definition使用不在接口定义中的派生类的公共方法
【发布时间】:2016-03-03 16:23:15
【问题描述】:

这里是 OOP 的新手。我用一种方法定义了一个接口,并在我的派生类中定义了另一种公共方法。我的客户端代码有条件地实例化一个接口类型的类,当然编译器不知道其中一个派生类中的方法,因为它不是底层接口定义的一部分。这就是我要说的:

public interface IFileLoader
{
    public bool Load();
}

public class FileLoaderA : IFileLoader
{
    public bool Load();
    //implementation

    public void SetStatus(FileLoadStatus status)
    {
        //implementation
    }

}

public class FileLoaderB : IFileLoader
{
    public bool Load();
    //implementation
    //note B does not have a SetStatus method
}

public enum FileLoadStatus
{
    Started,
    Done,
    Error
}
// client code
IFileLoader loader;

if (Config.UseMethodA) 
{
    loader = new FileLoaderA();        
}
else
{
    loader = new FileLoaderB();
}

//does not know about this method
loader.SetStatus (FileStatus.Done); 

我想我有两个问题:

  1. 我应该怎么做才能确定在运行时创建的对象是否具有我尝试使用的方法?还是我的方法不对?

  2. 我知道人们一直在谈论 IOC/DI。作为新的 OOP,使用 IOC 有什么好处可以说,“当我的应用程序询问 对于 IFileLoader 类型,使用具体类 x",而不是简单地 使用 App.Config 文件获取设置?

【问题讨论】:

  • 您必须将 loader 转换为具有额外方法的类型才能调用方法,例如 ((FileLoaderA)loader).SetStatus(FileStatus.Done);
  • 如果你需要测试你是否可以先使用 if((loader as FileLoaderA) != null)
  • 决定把上面的cmets放在一个答案里。
  • 为什么不在Load 方法的末尾加上FileLoaderA 调用SetStatus

标签: c# oop ioc-container


【解决方案1】:

参考您的两个问题和您的other post,我推荐以下内容:

我应该怎么做才能确定在运行时创建的对象是否具有我尝试使用的方法?还是我的方法不对?

您不一定需要在运行时在客户端代码中找出具体实现。遵循这种方法,您有点挫败了界面的关键目的。因此,天真地使用界面并让背后的具体逻辑决定要做什么是相当有用的。

所以在你的情况下,如果一个实现只是能够加载一个文件 - 很好。如果您的其他实现能够做到这一点并且更多一点,那也很好。但是客户端代码(在您的情况下是您的控制台应用程序)不应该关心它,只需使用Load()

也许有些代码能说一千多个单词:

public class ThirdPartyLoader : IFileLoader
{
    public bool Load(string fileName)
    {
        // simply acts as a wrapper around your 3rd party tool
    }
}

public class SmartLoader : IFileLoader
{
    private readonly ICanSetStatus _statusSetter;

    public SmartLoader(ICanSetStatus statusSetter)
    {
        _statusSetter = statusSetter;
    }

    public bool Load(string fileName)
    {
        _statusSetter.SetStatus(FileStatus.Started);
        // do whatever's necessary to load the file ;)
        _statusSetter.SetStatus(FileStatus.Done);
    }
}

请注意,SmartLoader 功能更多。但是作为关注点分离的问题,它的目的是加载部分。状态的设置是另一个班级的任务:

public interface ICanSetStatus
{
    void SetStatus(FileStatus fileStatus);
    // maybe add a second parameter with information about the file, so that an 
    // implementation of this interface knows everything that's needed
}

public class StatusSetter : ICanSetStatus
{
    public void SetStatus(FileStatus fileStatus)
    {
        // do whatever's necessary...
    }
}

最后,您的客户端代码可能如下所示:

static void Main(string[] args)
{
    bool useThirdPartyLoader = GetInfoFromConfig();
    IFileLoader loader = FileLoaderFactory.Create(useThirdPartyLoader);

    var files = GetFilesFromSomewhere();
    ProcessFiles(loader, files);
}

public static class FileLoaderFactory
{
    public static IFileLoader Create(bool useThirdPartyLoader)
    {
        if (useThirdPartyLoader)
        {
            return new ThirdPartyLoader();
        }
        
        return new SmartLoader(new StatusSetter());
    }
}

请注意,这只是一种 可能的方式来做您正在寻找的事情,而无需在运行时确定 IFileLoader 的具体实现。也许还有其他更优雅的方式,这进一步将我引向您的下一个问题。

我知道人们一直在谈论 IOC/DI。作为新的 OOP,与简单地使用 App.Config 文件来获取设置相比,使用 IOC [...] 有什么优势?

首先,分离类的职责总是一个好主意,特别是如果你想轻松地对你的类进行单元测试。在这些时刻,接口是您的朋友,因为您可以轻松地替换或“模拟”实例,例如利用NSubstitute。此外,小类通常更易于维护。

上面的尝试已经依赖于某种控制反转。 main 方法对如何实例化 Loader 几乎一无所知(尽管工厂也可以进行配置查找。然后 main 不会什么都不知道,它只会使用实例)。

概括地说:您可以使用像 NinjectCastle Windsor 这样的 DI 框架,而不是编写样板工厂实例化代码,这使您能够将绑定逻辑放入最适合您需要的配置文件中。

长话短说:您可以在 app.config 中简单地使用布尔值 appSetting 来告诉您的代码使用哪个实现。但是您可以改用 DI 框架,并利用其功能轻松实例化其他类。对于这种情况,它可能有点过大,但绝对值得一看!

【讨论】:

  • khlr:非常感谢您的周到回复。我现在更清楚了,是时候进一步研究这个想法了。您是否会说让您编写的任何类实现接口几乎总是一个好主意 - 至少为了单元测试能力?几乎总是这样吗?我喜欢让类保持非常小的单一职责并将不同的对象传递给其他对象以完成工作的想法。因此,在您的示例中,如果我要使用 DI 框架,那会取代工厂的使用/需要吗? DI 框架是工厂吗?
  • 从单元测试的角度来看,我想说:是的,接口几乎总是有用的。但实际上,我认为它们并不总是需要,有时甚至是不必要的“噪音”(无论如何:如果有疑问,我会使用一个,因为我认为最好针对接口而不是具体实现,因为后者通常会导致紧耦合)。是的,您是对的:如果您使用 DI 框架,则不需要工厂部分。是的,under the hood 可能会为你抽象出一些工厂 ;-)
【解决方案2】:

使用类似的东西:

if((loader as FileLoaderA) != null)
{
    ((FileLoaderA)loader).SetStatus(FileStatus.Done);
}
else
{
    // Do something with it as FileLoaderB type
}

IoC 通常用于您的类依赖于需要首先设置的另一个类的情况,IoC 容器可以实例化/设置该类的实例以供您的类使用并通常通过构造函数将其注入您的类.然后它会为您提供一个已设置并准备就绪的类实例。

编辑:

我只是想让代码简洁易懂。我同意这不是这段代码最有效的形式(它实际上执行了两次强制转换)。

为了确定特定演员表是否有效,Microsoft 建议使用以下表格:

var loaderA = loader as FileLoaderA;
if(loaderA != null)
{
    loaderA.SetStatus(FileStatus.Done);
    // Do any remaining FileLoaderA stuff
    return; 
}

var loaderB = loader as FileLoaderB
if(loaderB != null)
{
    // Do FileLoaderB stuff
    return; 
}

我不同意在 if 中使用 is。 is 关键字旨在确定对象是否从实现特定接口的类实例化,而不是强制转换是否可行。我发现它并不总是返回预期的结果(特别是如果一个类通过直接实现或继承基类来实现多个接口)。

【讨论】:

  • 如果您在 IF 语句中进行转换,您应该使用 is 而不是 as。但是为了获得更好的性能,您应该在 if 之前使用as,而不是在 if 主体内进行强制转换。
  • 谢谢。我的第一个想法是,“接口需要非常通用,所以它只需要一个 Load()”。但是在我的客户端代码中,我需要在调用 Load() 之后告诉数据库加载过程的状态。 SetStatus() 似乎 一方面它属于 Loader 类,但另一方面它又不属于。我想我只是想知道是否值得添加所有的铸造噪音,或者只是将 SetStatus() 放在界面中并强制任何用户提供该方法。但是,Status 方法需要特定于应用程序的参数,例如 id、枚举和可选的错误消息,因此它不是通用的
  • 另外,我读过课程应该很小,做一个的事情,并且把它做好。 Load() 和 SetStatus() 被认为是“一”件事还是两件事?我应该创建一个 Logger 接口,并提供一个 StatusLogger 类并改用它吗?或者我应该让 SetStatus() 成为我的静态 Program 类(这是一个控制台应用程序)上的一个静态方法?但这又似乎没有意义,因为从概念上讲,设置加载文件的状态与加载文件有关。伙计,一旦你开始尝试沿着这条路走下去,它就会进入真正的灰色地带。
猜你喜欢
  • 1970-01-01
  • 2010-11-01
  • 2011-01-30
  • 1970-01-01
  • 2010-12-06
  • 1970-01-01
  • 2016-09-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多