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