The Non-Virtual Interface idiom in D
Jeremie Pelletier
jeremiep at gmail.com
Sat Sep 26 10:27:45 PDT 2009
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
More information about the Digitalmars-d
mailing list