Implementing View Types in Python
Truly hiding implementation details
In object-oriented languages like Java, C#, or Kotlin, given a type T
, an associated view type TView
is used to expose a specific view (parts) of an object of type T
. This helps hide implementation details.
For example, in the following Kotlin example, Ledger
interface is used to provide access to a ledger while hiding the underlying implementation details, i.e., LedgerImpl
provides the functionalities of a ledger and it has a process
and container
members.
interface Ledger {
fun getValue(i: Int): Int?
}class LedgerImpl: Ledger {
val container = HashMap<Int, Int>()
override fun getValue(i: Int) = container.get(i)
fun process() {
// processing
}
}fun getLedger(): Ledger {
val c = LedgerImpl()
c.process()
return c as Ledger
}
Can we achieve the same in Python?
Yes, we can mimic the above code structure in Python as follows.
from abc import ABC
from collections import defaultdictclass Ledger(ABC):
def get_value(self, i: int) -> int:
passclass _LedgerImpl(Ledger):
def __init__(self):
self._container = defaultdict(int) def get_value(self, i: int) -> int:
return self._container[i] def process(self) -> None:
...
def facade() -> Ledger:
l = _LedgerImpl()
l.process()
return l
While _container
is marked as private by convention (i.e., the name with underscore prefix), callers of the facade
can still access _container
in the returned value as Python does not enforce access restrictions at runtime. So, the implementation details are not truly hidden.
Can we do better?
(Accidentally) Yes, we can do better. We can use namedtuple
support in Python to realize the view type.
from collections import defaultdict
from typing import Callable, NamedTupleclass Ledger(NamedTuple):
get_value: Callable[[int], int]class _LedgerImpl():
def __init__(self):
self._container = defaultdict(int) def process(self) -> None:
... def get_view(self) -> Ledger:
return Ledger(lambda x: self._container[x])def facade() -> Ledger:
l = _LedgerImpl()
l.process()
return l.get_view()
With this implementation, unless we thread our way thru the lambda function created in get_view
, the implementation details stay truly hidden when compared to the previous Python implementation.
Also, this implementation pattern relies on composition instead of inheritance. While the earlier implementation pattern can be changed to use composition, it still does not truly hide implementation details.
When should we use this pattern?
This pattern is ideal to use when implementation details need to be truly hidden.
And, here’s my yardstick for when should implementation details be truly hidden.
If clients of a library/program will respect the access restrictions communicated via conventions, then this pattern is not helpful/required, e.g., when modules within a library or program act as clients of other modules in a library or program. In these situations, simpler realizations of view types (e.g., the first python example) will suffice.
On the other hand, if clients depend on the implementation of a library/program (say, for performance reasons) when the current version of the library/program does not support the capabilities needed by the client programs, then this pattern can be helpful to truly hide the implementation.
Note
I stumbled on this pattern during my coding sessions. Since I found it to be interesting and useful, I blogged about it. That said, as with all patterns, use it only when it is required.