пятница, 31 июля 2015 г.

ToDo. Interfaces with partial implementation in the model

Original in Russian: http://programmingmindstream.blogspot.ru/2015/07/todo_36.html

Interfaces are usually small, self-sufficient and self-consistent.

A “good”, well-developed interface has 3 to 5 methods that are not derived from each other .

However, there are “monsters” like IStream, IStorage and IDataObject in which interface methods are EXPLICITELY derived from the others by default.

IDataObject is a perfect example since it has GetData and GetDataHere as well as EnumFormatEtc and GetCanonicalFormatEtc .

There are also small interfaces with methods derived from the other methods by default in it.

For example:

 ICString = interaface
  function S: PChar;
  function Len: Integer;
  function EQ(const anOther: ICString): Boolean;

It is obvious that EQ can be derived from S and Len methods.

But, “as usual”, the “poor fellows” are forced to implement such “default methods”.

Again and again, one by one, they make systematic errors.

What can be done to it?

Naturally, we can develop a “bundle” of default implementations as global methods and pin the developers down to call these methods.

We can simplly remove the EQ methods from the interface and replace them with a “bundle” of default methods.


Sometimes even OUTSIDE interfaces can be implemented in the code.

Sometimes it is also CONVENIENT to have default redefinable implementation.

So what is to do?

Well, we may add a tick to the model at the interface method “default implementation”.

Next, we add a mixin to code-generation process to the interface with such ticks, for example, a tick at ICString.EQ.

In this case, we get a mixin in code-generation:

 _CString_ = class(_CString_Parent_)
   function EQ(const anOther: ICString): Boolean; virtual;
function _CString_.EQ(const anOther: ICString): Boolean;
 // - default implementation here

Let our TCString class implement (in the model) the ICString interface.

As a result, we have the code:

 _CString_Parent_ = TInterfacedObject;
 {$Include CString.imp.pas}
 TCString = class(_CString_, ICString)
  function S: PChar;
  function Len: Integer;
  //function EQ(const anOther: ICString): Boolean;
  // - implemented before, in _CString_

We may also get the TCString1:

 _CString_Parent_ = TInterfacedObject;
 {$Include CString.imp.pas}
 TCString1 = class(_CString_, ICString)
  function S: PChar;
  function Len: Integer;
  function EQ(const anOther: ICString): Boolean; override;
  // - default implementation overriden here

As for me, this idea is interesting, I’ve been looking for it for a long time.

We may move on and add a tree-option “tick” - abstract/virtual/final.

The options:
abstract - we do default implementation.
virtual - we do virtual default implementation.
final - we do final (static) implementation.

+Victor Morozov
+Mikhail Kostitsyn
+Nikolay Zverev


About patterns and mixins
My own implementation of IUnknown and reference counting. And mixins
Briefly. Much about “mixins and patterns” (in Russian)
Once again about “mixins”, now seriously (in Russian)
Languages with mixins possible one way or another (in Russian)
Why UML (in Russian)

I guess it’ll be ready by tomorrow.

Though it is not simple. There are some pitfalls.

If we copy interface methods to te mixin, these methods are implemented in descendants.

As parasites.

If we do not copy them, it is not clear how to show them.

As an option, we can add an “extra cycle” like %o or %O.

Still, how can we find out if “the method has been implemented”?

Should we move it to the PureMixIn?

Actually, it is SIMPLE - we should not implement methods with a tick AT ALL.

Комментариев нет:

Отправить комментарий