简体   繁体   中英

Using a public method of derived class that is not in interface definition

New to OOP here. I have defined an interface with one method, and in my derived class I defined another public method. My client code is conditionally instantiating a class of the interface type, and of course the compiler doesn't know about the method in one of the derived classes as it is not part of the underlying interface definition. Here is what I am talking about:

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

I guess I have two questions:

  1. What should I be doing to find out if the object created at run-time has the method I am trying to use? Or is my approach wrong?

  2. I know people talk of IOC/DI all the time. Being new OOP, what is the advantage of using an IOC in order to say, "when my app asks for an IFileLoader type, use concrete class x", as opposed to simply using an App.Config file to get the setting?

Referring to your two questions and your other post I'd recommend the following:

What should I be doing to find out if the object created at run-time has the method I am trying to use? Or is my approach wrong?

You don't necessarily need to find out the concrete implementation at runtime in your client code. Following this approach you kinda foil the crucial purpose of an interface. Hence it's rather useful to just naïvely use the interface and let the concrete logic behind decide what's to do.

So in your case, if one implementation's just able to load a file - fine. If your other implementation is able to the same and a bit more, that's fine, too. But the client code (in your case your console application) shouldn't care about it and just use Load() .

Maybe some code says more than thousand words:

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

Note that the SmartLoader does a bit more. But as a matter of separation of concerns its purpose is the loading part. The setting of a status is another class' task:

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

Finally your client code could look something like the follwing:

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

Note that this is just one possible way to do what you're looking for without having the necessity to determine IFileLoader 's concrete implementation at runtime. There maybe other more elegant ways, which furthermore leads me to your next question.

I know people talk of IOC/DI all the time. Being new OOP, what is the advantage of using an IOC [...], as opposed to simply using an App.Config file to get the setting?

First of all separating of classes' responsibility is always a good idea especially if you want to painlessly unittest your classes. Interfaces are your friends in these moments as you can easily substitute or "mock" instances by eg utilizing NSubstitute . Moreover, small classes are generally more easily maintainable.

The attempt above already relies on some sort of inversion of control. The main -method knows barely anything about how to instantiate a Loader (although the factory could do the config lookup as well. Then main wouldn't know anything, it would just use the instance).

Broadly speaking: Instead of writing the boilerplate factory instantiation code, you could use a DI-Framework like Ninject or maybe Castle Windsor which enables you to put the binding logic into configuration files which might best fit your needs.

To make a long story short: You could simply use a boolean appSetting in your app.config that tells your code which implementation to use. But you could use a DI-Framework instead and make use of its features to easily instantiate other classes as well. It may be a bit oversized for this case, but it's definitely worth a look!

Use something like:

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

IoC is normally used in situations where your class depends on another class that needs to be setup first, the IoC container can instantiate/setup an instance of that class for your class to use and inject it into your class usually via the constructor. It then hands you an instance of your class that is setup and ready to go.

EDIT:

I was just trying to keep the code concise and easy to follow. I agree that this is not the most efficient form for this code (it actually performs the cast twice).

For the purpose of determining if a particular cast is valid Microsoft suggests using the following form:

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

I do not agree with using is in the if. The is keyword was designed to determine if an object was instantiated from a class that implements a particular interface, rather than if a cast is viable. I have found it does not always return the expected result (especially if a class implements multiple interfaces through direct implementation or inheritance of a base class).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM