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