Design – Understanding ‘Immutable’ Interfaces in Object-Oriented Design

designimmutabilityinterfacesnamingobject-oriented

I am confused about the notion of immutability. Consider the following structure of a simplistic calculator:

Class diagram

Here are the interfaces:

interface IOperationalInterface {
    int Sum(int a, int b);
}

interface IAuditInterface {
    int SumInvocations();
}

IOperationalInterface.Sum should compute the sum of two integers and IAuditInterface.SumInvocations should return the total number of received Sum calls.

Here is a trivial implementation:

class CalculatorImpl : IOperationalInterface, IAuditInterface {
    private int invocations = 0;

    public int Sum(int a, int b) {
        invocations++;
        return a + b;
    }

    public int SumInvocations() {
        return invocations;
    }
}

Is CalculatorImpl immutable? Obviously not, because its state changes with each invocation of the Sum method. Is the Sum operation pure? According to Wikipedia it is not, since it changes the state of a mutable object, and that side-effect is observable through the IAuditInterface. Obviously the SumInvocations operation is also not pure, since it may return different results.

In summary, CalculatorImpl is mutable and all of its methods are impure.

However, from the viewpoint of CalculatorClient, who talks to it only through the IOperationalInterface, it appears to be immutable and the Sum operation appears to be pure in the sense that it could not observe any side effects through that interface.

On the other hand, from the perspective of AuditClient, it is completely different: it is obvious that the object implementing the AuditInterface is mutable and its SumInvocations operation is impure, and this follows directly from the specification of the IAuditInterface.

So, it is possible to partition an interface/specification of a mutable class such that some parts of it will appear mutable and some not. In this case, considering only the Sum operation and leaving out the requirement that the invocations should be counted, we get something that does not have any side-effects.

Now, on implementing the CalculatorClient one can take into account the fact that the object behind the interface appears to be immutable. At least one could not tell the difference.

So my question is: does it make sense to talk about "immutability" of interfaces or is it a bad idea? How else can I communicate the fact that there will be no observable side-effects through that interface? And if it's bad, what could go wrong?

UPDATE

Thanks for your answers/comments! I see now that there is no way to say that the IOperationalInterface is pure; the conditions of purity are much too strong to apply in this case. However the question remains whether there is a weaker notion (maybe "immutability"?) which is applicable.

Best Answer

However, from the viewpoint of CalculatorClient, who talks to it only through the IOperationalInterface, it appears to be immutable and the Sum operation appears to be pure in the sense that it could not observe any side effects through that interface.

Whatever you can observe through the interface is irrelevant to the concept of purity. If Sum logged results to a file, it would still be impure even though you wouldn't be able to observe any changes through the interface.

Purity is a very strong condition. If you say that something is pure, I'll take you at your word and assume nothing bad will happen if I run it from multiple threads, cache the results and only call it once for any given set of inputs, or that I can call it 1,000,000 times without exhausting the OS's file handles. Those assumptions can backfire when using CalculatorImpl.

There's nothing wrong with partitioning the interfaces the way you have, but you should be precise. The specification you want for IOperationalInterface is probably closer to: "The return value of these methods must depend only on their arguments, but their execution may have side effects." You can be more specific about the side effects and say that the methods must not perform any kind of I/O. But I wouldn't say that it's pure, because you're very clearly not using it that way.

Related Topic