Leaky abstractions and how to plug some

Every once in a while you run into some code that tries to present a fairly abstract API to some class / module. Maybe this is in your own code or in code you have received from some other source.

But as you work with it you find that the abstraction reveals internal implementation details that really should not be known outside the class. One way to hide these sorts of details from other portions of your code is to use an API defined in an interface.

However, even that doesnt always work.

Suppose you have some set of classes where you want to make it possible for one to contain many of the others and that the internal storage mechanism should be invisible, or at least not easily guessable, from other classes. Something like you see in the IDE where you have Folders that can contain all kinds of other items, or Modules that can also contains other items.

It would make sense for the classes that represent such a Container type to implement the Xojo.Core.Iterable interface so you could use either in a For … Next loop.

Lets start by assuming we’re going to create a set up like

Class ContainerType
   Implements Xojo.Core.Iterator

   Function MoveNext() as Boolean
   End Function

   Function Value() as Auto
   End Function
End Class

Class FolderType
   Inherits ContainerType
End Class

Class ModuleType
   Inherits ContainerType
End Class

We’ll have a base ContainerType class and then two subclasses*. The names of the classes I’ve chosen don’t conflict with built in ones.

At this point you might think you could write

Dim f As New ContainerType

For Each foo As Auto In f
    
Next

but you can’t. In a for .. each you need an item that implements Xojo.Core.Iterator – not Xojo.Core.Iterable.

OK we need an iterator so we’ll add one like

Class ContainerTypeIterator
  Implements Xojo.Core.Iterator

   Function MoveNext() as Boolean
   End Function

   Function Value() as Auto
   End Function
End Class

As well we need to make our initial class implement Xojo.Core.Iterable

Class ContainerType
   Implements Xojo.Core.Iterable

   Function GetIterator() as Xojo.Core.Iterator
   End Function

End Class

Class FolderType
   Inherits ContainerType
End Class

Class ModuleType
   Inherits ContainerType
End Class

And now we can write

Dim f As New ContainerType // or new FolderType or new ModuleType

For Each foo As Auto In f
    
Next

But how do we make the Iterator itself work ? Unfortunately there don’t seem to be any Xojo code examples and the docs are kind of light on this.

The iterator isnt a “friend” of our base class so it cannot reach into its guts and grab whatever data it wants. Especially not if that data is protected or private in any way. So our Iterator needs some api to be able to get the data it needs from our ContainerType classes. So we need an API for that.

But whatever API we put on the ContainerType class is also going to be usable outside the Iterator. There’s no way straight forward simple way to restrict an API to ONLY being by one class in Xojo. (This is where “friend” classes would be handy as they could have a “special” relationship and be allowed to use some private api that no other class could – there are hacky ways to achieve this though)

And so however you implement the Iterator & its access to the individual members of each class that implements Iterable those API’s are usable by ANY other code. And so that abstraction of “iterable” and how it’s implemented leaks out to the rest of the world.

Stopping this leakage can be done – in a limited way. In a module you can have an interafce that is private to that module and then classes that are in that module can implement that interface. Only methods and other classes in that module can then use that interface.

Module Containers
  Private Interface PrivateContainerAccessors
     Function MoveNext() as boolean
     Function Value() as Variant
  End Interface

  Class ContainerType
    Implements Xojo.Core.Iterable

    Function GetIterator() as Xojo.Core.Iterator
    End Function

     Function MoveNext() as boolean
     End Function

     Function Value() as Variant
     End Function

  End Class

  Class FolderType
    Inherits ContainerType

     Function MoveNext() as boolean
     End Function

     Function Value() as Variant
     End Function

  End Class

  Class ModuleType
    Inherits ContainerType
     Function MoveNext() as boolean
     End Function

     Function Value() as Variant
     End Function

  End Class
End Module
      

And now only classes IN the module can cast the classes that implement this interface in a way they can call the interface methods. This is quite handy – but it does have a drawback. Nothing outside this module can implement that interface. And that limits its usefulness to code you write and put in that module.

Friend scope would be really handy. In the mean time this is as close as you get and can stop your Iterable abstraction and the implementation from leaking out into the rest of your code.

*This really is JUST an example and not actual Xojo IDE code for those of you who might be suspiciously minded – I can assure you the IDE is vastly different than this – this is JUST an example that you can relate to from using the IDE