Search code examples
c#solid-principlesliskov-substitution-principle

Is this breaking LSP - Post condition rule


Wanted to know if I am breaking Liskov Substitution Principle - post-condition rule here,

public class ProcessController
{
    public virtual Dictionary<int, string> GetRunningProcess()
    {
        Dictionary<int, string> processes = new()
        {
            { 1, "system" },
            { 44, "system" },
            { 45, "user" },
            { 102, "user" },
            { 176, "user" },
            { 274, "application" },
            { 188, "application" }
        };
        return processes.Where(x => !x.Value.Equals("application")).ToDictionary();
    }
}

public class UserProcessController : ProcessController
{
    public override Dictionary<int, string> GetRunningProcess()
    {
        Dictionary<int, string> processes = new()
        {
            { 1, "system" },
            { 44, "system" },
            { 45, "user" },
            { 102, "user" },
            { 176, "user" },
            { 274, "application" },
            { 188, "application" }
        };
        Dictionary<int, string> userProcess = processes.Where(x => x.Value.Equals("user")).ToDictionary();
        return userProcess;
    }
}

public class ApplicationProcessController : ProcessController
{
    public override Dictionary<int, string> GetRunningProcess()
    {
        Dictionary<int, string> processes = new()
        {
            { 1, "system" },
            { 44, "system" },
            { 45, "user" },
            { 102, "user" },
            { 176, "user" },
            { 274, "application" },
            { 188, "application" }
        };
        Dictionary<int, string> userProcess = processes.Where(x => x.Value.Equals("application")).ToDictionary();
        return userProcess;
    }
}

LSP: Postcondition cannot be weakened but strengthened.

The parent does not return process of type of application.

The first child UserProcessController return a process type of user but does not returns application and system. - Is the weakening the postcondition?

The second child ApplicationProcessController return a process type of application but does not returns user and system. - Is the weakening the postcondition?

what work around can I perform to compliance with LSP in this case.


Solution

  • Just to make sure we are on the same page, I will first explain what a "postcondition" is.

    After calling ProcessController.GetRunningProcess, what are the things that the caller can reason about their program? Those are its postconditions. This is unrelated to the implementation of GetRunningProcess. The implementation of course needs to satisfy the postconditions, but otherwise it does not dictate what the postconditions are. You should decide how the callers of this method can reason about its return value.

    Some postconditions are already encoded in the method declaration, and the typechecker helps to enforce them. For example, GetRunningProcess is declared to return a Dictionary<int, string> - that is a postcondition. The caller is guaranteed to get a Dictionary<int, string> after calling GetRunningProcess. A subclass cannot override it and change the return type to object, because then the caller is not guaranteed to get a Dictionary<int, string> anymore (hence weakening the postcondition). The method can now return a string, for example.

    Other than the method declarations, you did not mention what other postconditions GetRunningProcess has. As far as the postconditions encoded in the method declaration, neither subclasses change the postconditions. All 3 methods are declared to return Dictionary<int, string>.

    A reasonable postcondition for ProcessController.GetRunningProcess is

    returns a dictionary representing the running processes. The keys of the dictionary represents the process ids, and the values represent the type of the process.

    And a reasonable postcondition for UserProcessController.GetRunningProcess might be:

    returns a dictionary representing the running processes. The keys of the dictionary represents the process ids, and the values represent the type of the process. The type of the processes is always "user".

    Then that will be strengthening the postcondition of ProcessController.GetRunningProcess. I should repeat that this is unrelated to how these methods are implemented. You should decide what the postconditions are, and the implementation is correct as long as it satisfies the postconditions, but otherwise the implementation can do anything it wants.


    If the postcondition of ProcessController.GetRunningProcess is instead:

    returns a dictionary [...]. The types of the processes is never "application".

    and the postcondition of ApplicationProcessController.GetRunningProcess is:

    returns a dictionary [...]. The type of the processes is always "application".

    Then this would violate LSP, because ApplicationProcessController changed the postcondition completely.