Andrei Alexandrescu wrote:
Jeremie Pelletier wrote:
Andrei Alexandrescu wrote:
In this article:

http://www.gotw.ca/publications/mill18.htm

Herb Sutter makes a powerful argument that overridable functions (customization points) should actually not be the same as the publically available interface. This view rhymes with the Template Method pattern as well.

This leads to the interesting setup in which an interface should ideally define some signatures of to-be-defined functions, but disallow client code from calling them. For the clients, the same interface should expose some higher-level final functions.

Ignoring for the moment access protection semantics in D (which are broken anyway), let's say this would work:

interface Cloneable(T) if (is(T == class))
{
    private T doClone(); // must implement but can't call
    T clone()            // this is what everybody can call
    {
        auto result = doClone();
        assert(typeof(result) == typeof(this));
        assert(this.equals(result));
        return result;
    }
}

So clients must implement doClone, but nobody can ever call it except Cloneable's module. This ensures that no cloning ever gets away with returning the wrong object.

Pretty powerful, eh? Now, sometimes you do want to allow a derived class to call the base class implementation. In that case, the interface function must be protected:

interface ComparableForEquality(T)
{
    protected bool doEquals(T);
    final bool equals(T rhs)
    {
        auto result = doEquals(rhs);
        assert(rhs.equals(cast(T) this) == result);
        return result;
    }
}

The difference is that now a derived class could call super.doEquals.

This feature would require changing some protection rules, I think for the better. What do you think?


Andrei

What about:

interface ComparableForEquality(T) {
    bool equals(T rhs)
    out(result) {
        assert(rhs.equals(cast(T)this) == result);
    }
}

I want to cajole Walter into implementing contracts on interfaces as well.

Getting instead a contract that gets added to implementations, or at least only the base implementation, and letting the actual method unimplemented.

However having code for interfaces as well as protection would be neat. They could prevent a lot of template mixins within every implementation to get a common feature.

Note that NVI is about more than pre- and post-conditions. I'm actually amazed that so many people latched so firmly onto the examples I gave (as opposed to e.g. the examples in Herb's article).


Andrei

I agree, pre- and post-conditions are usually things that change very little across implementations and would therefore be best suited in NVI than repeated across all implementations.

However I think interfaces could allow for an optionnal body as well, and even templated methods, here's an problem I had with my I/O interfaces:

interface IInputStream : IStream {
size_t read(ubyte[] buf); // Read data up to buf.length, returns actual read bytes.

        void read(S : S*)(S* s) { // specialized read for primitives and structs
                if(read((cast(ubyte*)s)[0 .. S.sizeof]) != S.sizeof)
                        throw new ReadException();
        }
}

This is something you just can't do right now, yet would be extremely useful to provide method variants that call into a method of the implementation.

The way I worked around it right now was to add a bunch of specialized prototypes for every primitive size (readByte, readShort, readInt, readLong, readStruct) and declare a mixin template to implement them in every IInputStream implementation.

Jeremie

Reply via email to