If you are not familiar with the Non Virtual Interface idiom I recommend that you go and read about it. D takes this idiom to the next level by allowing to have tempalted methods within interfaces if they are final. This can be very powerfull and help reducing boilerplate code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import std.traits; import std.string : toStringz; import core.stdc.stdio; interface IInputStream { public: final size_t read(T)(ref T data) if(!isArray!T) { static assert(!is(T == const) && !is(T == immutable), "can not read into const / immutable value"); return readImpl(&data, T.sizeof, 1); } final size_t read(T)(T data) if(isArray!T) { static assert(!is(typeof(data[0]) == const) && !is(typeof(data[0]) == immutable), "can not read into const / immutable array"); return readImpl(data.ptr, typeof(data[0]).sizeof, data.length); } protected: size_t readImpl(void* data, size_t elementSize, size_t elementCount); } class FileInputStream : IInputStream { private: FILE* m_handle; public: this(string filename) { m_handle = fopen(toStringz(filename), "r"); } protected: override size_t readImpl(void* data, size_t elementSize, size_t elementCount) { return fread(data, elementSize, elementCount, m_handle); } } |
If you look at the above example you can see that the IInputStream interface has two templated methods which are final. Thos methods implement the behavior needed for arrays and value types. Then they forward the read in the correct way to readImpl. As a result implementing the IInputStream interface becomes really easy. The only method that needs to be implemented is the readImpl method as you see from the FileInStream class. The correct handling of different types only has to be implemented once, inside the interface, and all implementations don’t have to care about this anymore. You could also think of something similar for serializing data. A ISerializer interface which implementes all the needed type handling as templates and forwards it to simple protected methods. Then implementing different serialization targets (e.g. json, xml, binary, etc) would be quite easy and not require writing and testing template code anymore. A further advantage of this idiom is, that it lowers the amout of code bloat because the templates only get generated once for the interface and are reused by every implementation.
The above code was tested with dmd 2.063.2