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 defaultdict
class Ledger(ABC):
def get_value(self, i: int) -> int:
pass
class _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 abc import ABC
from collections import defaultdict
from typing import Callable, NamedTuple
class 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 them only when they are required.

Programming, experimenting, writing | Past: SWE, Researcher, Professor | Present: SWE

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store