使用不在接口定义中的派生类的公共方法

本文关键字:派生 方法 接口 定义 | 更新日期: 2023-09-27 18:37:10

这里对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 文件获取设置?

使用不在接口定义中的派生类的公共方法

参考您的两个问题和您的其他帖子,我建议以下内容:

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

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

所以在你的情况下,如果一个实现只能加载一个文件 - 很好。如果你的其他实现能够相同甚至更多,那也没关系。但是客户端代码(在您的情况下是控制台应用程序)不应该关心它,而只使用 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);
    }
}

请注意,智能加载程序的功能更多。但作为关注点分离的问题,其目的是装载部分。状态的设置是另一个类的任务:

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-方法对如何实例化加载器几乎一无所知(尽管工厂也可以进行配置查找。然后 main 什么都不知道,它只会使用实例)。

从广义上讲:您可以使用像 Ninject 或 Castle Windsor 这样的 DI-Framework,而不是编写样板工厂实例化代码,它使您能够将绑定逻辑放入最适合您需求的配置文件中。

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

使用类似以下内容:

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 关键字旨在确定对象是否从实现特定接口的类实例化,而不是强制转换是否可行。我发现它并不总是返回预期的结果(特别是如果一个类通过直接实现或继承基类来实现多个接口)。