Search code examples
c#interfacesolid-principles

Interface Segregation Principle on a collection of objects


I am studying on the SOLID principles and the interface segregation principle is giving me a hard time to understand in a scenario like this one.

Basically, I have a collection of objects... users... that use an interface.

public interface IUserInfo 
{
    string Name { get; }
    string Extension { get; }
}

The issue is that there are some cases where a class that inherits this interface will not use an extension. So to get around that, it simply returns an empty string.

Now, technically this is fine because the string is bound to the UI. So it would simply display an empty string for that field.

However, this violates the Interface Segregation Principle. What is suggested is to split those interfaces.

But then I run into problems where I want this interface in a collection, consider this:

public interface IUserExtension : IUserInfo
{
    string Extension { get; }

}
    public ObservableCollection<IUserInfo> StoredUserInfos { get; set; } = new ObservableCollection<IUserInfo>()
    {
        new User1(),
        new User2(),
    };

    public class User1 : IUserExtension
    {
        public string Name { get; } = "Alex";
        public string Extension { get; } = "(715) 925";

        public override string ToString()
        {
            return Name;
        }
    }

    public class User2 : IUserInfo
    {
        public string Name { get; } = "Daniel";
        public override string ToString()
        {
            return Name;
        }
    }

I cant bind to the index of this collection and obtain the Extension, because it is an IUserInfo interface. Coincidentally, I also cannot make this a collection of IUserExtension because the User2 class doesn't implement it.

The only way for this to work would be to use pattern matching as far as I am aware

        if (UserIndex != -1)
        {
            var userInfo = StoredUserInfos[UserIndex];
            Extension = userInfo is IUserExtension info ? info.Extension : "No Extensions";
            Name = userInfo.Name;
        }

But that adds a bit of complexity to the code and it may violate other rules because it is checking for the type.

In this case, would the best solution instead would be to implement an abstract class that implements the interface and the default implementation would be an empty string?


Solution

  • This is a difficult one, but I'm going to stick my neck out and attempt to answer.

    Does including Extension in the IUserInfo interface violate ISP? I think yes and no.

    Let's imagine you want to include a MiddleName property in your IUserInfo interface. A middle name is something that not everyone has but you probably wouldn't want to add that to a separate interface. Along with the Name property, they are a cohesive unit, i.e. they belong together.

    In this case, you would probably want to return an empty string (as you have above) or such like. (For more complicated objects with behaviour the Null Object pattern is a much better solution.)

    So if we return to your problem above, do you see Extension as something that belongs in an interface on its own? Probably not. Again, let's imagine adding EmailAddress to the IUserInfo interface. Along with Extension, they make a cohesive unit and conceptually represent contact details. If you were to extract these into a separate interface, you would still have some users that do not have an extension in which case you would want to return an empty string (or similar).

    So, in answer to your question, I think you could keep Extension in the IUserInfo interface if you are unlikely to extend the interface with any more properties. Otherwise, create an interface that represents the concept of contact details but still returns an empty string if the user doesn't have an extension.