1

I have an implementation of Interfaces that I must solve: Here's an example:

interface ISettingsBase
{
    string Name { get; set;}
    DateTime TimeStamp { get; set; }
}

public class SettingsBase : ISettingsBase
{
    public string Name { get; set; }
    public DateTime TimeStamp { get; set; }
}

interface IWorkerBase
{
    ISettingsBase Settings { get; set; }
}

public class WorkerBase: ISettingsBase
{
    public ISettingsBase Settings { get; set; }
}

interface IExtendedSettings : ISettingsBase
{
    string FilePath { get; set; }
}

interface IWorkerExtended : IWorkerBase
{
    // This configuration property should respect those of
    // the IWorkerBase and increase the features.
    IExtendedSettings Settings { get; set; }
}

public class WorkerExtended : WorkerBase, IWorkerExtended
{
    ...
    ...
    public IExtendedSettings Settings { get; set; }
}

The problem is that the compiler tells me that there is an error in WorkerExtended, and that I am not respecting the implementation of the IWorkerBase.Settings interface. The problem is that I need the new improved classes also support configurations with more properties.

4
  • It just feels like you've got too many interfaces and too many implementations. How would your client know that it got an instance of WorkerExtended (and not just an instance of IWorkerBase) and that calling its Settings would return an instance of IExtendedSettings? Commented Aug 3, 2017 at 15:48
  • @user270576 Sorry I should have been more detailed in the example, I can not copy the original code because it is a bit more complex and extensive. Actually WorkerBase should be an abstract class with some basic functionality. I think the original idea is to force Workers to have a basic configuration and common functionality but with variations that make them different, so they also have additional configurations. Commented Aug 3, 2017 at 15:53
  • that still does not answer my question: how would a consumer class know that it has an instance of IWorkerExtended and not an instance of IWorkerBase? How would it know to call myWorker.Settings.FilePath? Commented Aug 3, 2017 at 16:06
  • Everything is related to serialization methods of the XML configurations. When a Worker is loaded another module queries the Type, something like: ... if (worker is IWorkerExtended) string path = ((IWorkerExtended) worker) .Settings.FilePath; Commented Aug 3, 2017 at 16:11

2 Answers 2

4
interface IWorkerBase
{
    ISettingsBase Settings { get; set; }
}

interface IWorkerExtended : IWorkerBase
{
    IExtendedSettings Settings { get; set; }
}

This is already problematic because IWorkerExtended.Settings will hide the Settings member that IWorkerBase requires. So implementers of IWorkerExtended will still have to provide the original Settings member (of type ISettingsBase) in order to transitively implement IWorkerBase.

The compiler will warn you here because of this member hiding that is going on here. Usually doing so will be a mistake, so you need to use the new keyboard to express that you willingly want to do this:

interface IWorkerExtended : IWorkerBase
{
    new IExtendedSettings Settings { get; set; }
}

Note that this does not have an affect on implementers though. So when implementing that interface, you will have to provide two Settings members. You can do that by explicitly implementing the base interface:

public class Worker : IWorkerExtended
{
    public IExtendedSettings Settings { get; set; }

    ISettingsBase IWorkerBase.Settings { get; set; }
}

The reason for this is very simple: The Liskov substitution principle says that when IWorkerExtended is a subtype of IWorkerBase, then any object of type IWorkerBase can be replaced by an object of type IWorkerExtended. Now consider this:

IWorkerBase baseWorker = GetExtendedWorker();
baseWorker.Settings = new SettingsBase(); // not extended

This will only work if you can assign a SettingsBase to Settings which is what the type IWorkerBase guarantees (since SettingsBase is a subtype of ISettingsBase). So in order for the extended worker to be assignable to the base worker type, it needs to guarantee that you can set a ISettingsBase to it. But if it would just implement the IWorkerExtended, you could just assign the much more specific extended settings to Settings.

Sign up to request clarification or add additional context in comments.

2 Comments

As I said in the previous comment to user270576. I think the original idea is to force Workers to have a basic configuration and common functionality but with variations that make them different, so they also have additional configurations. The question is, is the approach bad at all? The "new" key word would apply as a kind of correction? I mean is there a more correct way to raise the problem?
It’s kind of difficult to design this properly without knowing more about the system in general. But what you could do is separate the strict relationship between the extended worker and the extended settings. Every worker type would just expose the ISettingsBase Settings. And those settings could alternatively be the simpler or the more extended settings. If the extended worker itself also relies on the extended settings, maybe having a separate member with a different name is more appropriate than hiding the other member. – But what is “correct” here is difficult to say…
0

Interfaces are meant to hide implementation details, not to tell consumer that "oh-yeah-this-guys-has-some-additional-fields".

As a rule, casting or type-checking like if (worker is IWorkerExtended) is a sign that you don't need interfaces at all.

I would say that in this case you would be better off just having a bunch of worker classes implementing an empty IWorker interface.

Something like:

public interface IWorker
{
}

public class SettingsBase
{
    public string Name { get; set; }
    public DateTime TimeStamp { get; set; }
}

public class ExtendedSettingsA : SettingsBase
{
    public string FilePath { get; set; }
}

public class ExtendedSettingsB : SettingsBase
{
    public string SomeOtherProp { get; set; }
}

public class WorkerBase : IWorker
{
    public SettingsBase Settings { get; set; }
}

public class WorkerExtendedA : IWorker
{
    public ExtendedSettingsA Settings { get; set; }
}

public class WorkerExtendedB : IWorker
{
    public ExtendedSettingsB Settings { get; set; }
}


public class XmlLoader
{
    public IWorker Load(string xml)
    {
        return null; // instance of WorkerBase or WorkerExtendedA or WorkerExtendedB
    }
}

public class Consumer
{
    public void Process(IWorker w)
    {
        if (w is WorkerBase)
        {
            WorkerBase wbase = w as WorkerBase;
            string name = wbase.Settings.Name;
            DateTime t = wbase.Settings.TimeStamp;
        }
        if (w is WorkerExtendedA)
        {
            WorkerExtendedA wa = w as WorkerExtendedA;
            string name = wa.Settings.Name;
            DateTime t = wa.Settings.TimeStamp;
            string f = wa.Settings.FilePath;
        }
        if (w is WorkerExtendedB)
        {
            WorkerExtendedB wb = w as WorkerExtendedB;
            string name = wb.Settings.Name;
            DateTime t = wb.Settings.TimeStamp;
            string other = wb.Settings.SomeOtherProp;
        }
    }
}

3 Comments

Nor do I think it is a viable solution, because in reality the worker has methods and properties established, apart from the Settings that must be serialized.These common methods and props must be governed by a contract.
That is why the interfaces were defined, to be able to add new Workers and better ways of doing things. ie: a worker could perform a process that requires calculations, another improved worker could do the same but invoking GPU, both would be valid implemented differently and all this avoiding doing hardcoded, I think that making use of a List of available workers, perhaps using reflection or using a method of registering a worker with an extra lib.
@Cheva apply same concept to Settings then: have a Settings method in IWorkerBase return ISettings, do not overload this method and do not overload ISettings. Have your serialized objects implement ISettings directly. In your consumer instead of if (worker is IWorkerExtended) do if (worker.Settings is ExtendedSettingsA)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.