DEV Community

Cover image for Implementing View Types in Python
Venkatesh-Prasad Ranganath
Venkatesh-Prasad Ranganath

Posted on

Implementing View Types in Python

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

While _container is marked as private by convention (i.e., the name is prefixed with an underscore), callers of 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()
Enter fullscreen mode Exit fullscreen mode

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 the client programs of a library/program will respect the access restrictions communicated via conventions, then this pattern is not helpful/required. This is most likely the case when modules within a library or program act as clients of other modules in a library or program. In these situation, simpler realizations of view types (e.g., the first python example) will suffice.

On the other hand, if client programs may take dependence on the implementation of a library/program (e.g., for performance reasons) when the current version of the library/program does not support the capabilities need 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.

(Originally posted here.)

Top comments (0)