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