7.4. Polymorphism

7.4.1. Procedural

  • UNIX getchar() function used function lookup table with pointers

>>> keyboard = {
...     'open': lambda: ...,
...     'close':  lambda: ...,
...     'read':  lambda bytes: ...,
...     'write':  lambda content: ...,
...     'seek':  lambda position: ...,
... }
>>>
>>> file = {
...     'open': lambda: ...,
...     'close':  lambda: ...,
...     'read':  lambda bytes: ...,
...     'write':  lambda content: ...,
...     'seek':  lambda position: ...,
... }
>>>
>>> socket = {
...     'open': lambda: ...,
...     'close':  lambda: ...,
...     'read':  lambda bytes: ...,
...     'write':  lambda content: ...,
...     'seek':  lambda position: ...,
... }
>>>
>>>
>>> def getchar(obj):
...     obj['open']()
...     obj['seek'](0)
...     obj['read'](1)
...     obj['close']()
>>>
>>>
>>> getchar(file)
>>> getchar(keyboard)
>>> getchar(socket)

7.4.2. Explicit

>>> from abc import ABC, abstractmethod
>>>
>>>
>>> class Element(ABC):
...     def __init__(self, name):
...         self.name = name
...
...     @abstractmethod
...     def render(self) -> str:
...         raise NotImplementedError
>>>
>>>
>>> class InputText(Element):
...     def render(self):
...         return f'  <input type="text" name="{self.name}" />'
>>>
>>>
>>> class InputPassword(Element):
...     def render(self):
...         return f'  <input type="password" name="{self.name}" />'
>>>
>>>
>>> class InputButton(Element):
...     def render(self):
...         return f'  <input type="submit" name="{self.name}" />'
>>>
>>>
>>> def render(component: list[Element]):
...     print('<form method="post" action="/login">')
...     for element in component:
...         print(element.render())
...     print('</form>')
>>>
>>>
>>> login_form: list[Element] = [
...     InputText('username'),
...     InputPassword('password'),
...     InputButton('login'),
... ]
>>>
>>> render(login_form)
<form method="post" action="/login">
  <input type="text" name="username" />
  <input type="password" name="password" />
  <input type="submit" name="login" />
</form>

7.4.3. Structural

  • Duck Typing

>>> from typing import Protocol
>>>
>>>
>>> class InputText:
...     def __init__(self, name):
...         self.name = name
...
...     def render(self):
...         return f'  <input type="text" name="{self.name}" />'
>>>
>>>
>>> class InputPassword:
...     def __init__(self, name):
...         self.name = name
...
...     def render(self):
...         return f'  <input type="password" name="{self.name}" />'
>>>
>>>
>>> class InputButton:
...     def __init__(self, name):
...         self.name = name
...
...     def render(self):
...         return f'  <input type="submit" name="{self.name}" />'
>>>
>>>
>>> class Renderable(Protocol):
...     def render(self) -> str: ...
>>>
>>>
>>> def render(component: list[Renderable]):
...     print('<form method="post" action="/login">')
...     for element in component:
...         print(element.render())
...     print('</form>')
>>>
>>>
>>> login_form: list[Renderable] = [
...     InputText('username'),
...     InputPassword('password'),
...     InputButton('login'),
... ]
>>>
>>> render(login_form)
<form method="post" action="/login">
  <input type="text" name="username" />
  <input type="password" name="password" />
  <input type="submit" name="login" />
</form>

7.4.4. Use Case 1

>>> from abc import ABC, abstractmethod
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class Account(ABC):
...     username: str
...
...     @abstractmethod
...     def login(self):
...         pass
>>>
>>>
>>> class User(Account):
...     def login(self):
...         return f'User login: {self.username}'
>>>
>>> class Admin(Account):
...     def login(self):
...         return f'Admin login: {self.username}'
>>>
>>>
>>> def login(accounts: list[Account]) -> None:
...     for account in accounts:
...         print(account.login())
>>>
>>>
>>> group = [
...     User('mwatney'),
...     Admin('mlewis'),
...     User('rmartinez'),
...     User('avogel'),
... ]
>>>
>>> login(group)
User login: mwatney
Admin login: mlewis
User login: rmartinez
User login: avogel

In Python, due to the duck typing and dynamic nature of the language, the Interface or abstract class is not needed to do polymorphism:

>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class User:
...     username: str
...
...     def login(self):
...         return f'User login: {self.username}'
>>>
>>> @dataclass
... class Admin:
...     username: str
...
...     def login(self):
...         return f'Admin login: {self.username}'
>>>
>>>
>>> group = [
...     User('mwatney'),
...     Admin('mlewis'),
...     User('rmartinez'),
...     User('avogel'),
... ]
>>>
>>> for account in group:
...     print(account.login())
User login: mwatney
Admin login: mlewis
User login: rmartinez
User login: avogel