8.6. Prototype¶
EN: Prototype
PL: Prototyp
Type: object
8.6.1. Pattern¶
Create new object by copying an existing object

8.6.2. Problem¶
Violates Open/Close Principle

from abc import ABC, abstractmethod
from dataclasses import dataclass
class Component(ABC):
@abstractmethod
def render(self) -> None: ...
@dataclass
class Circle(Component):
radius: int | None = None
color: str | None = None
def render(self) -> None:
print('Rendering circle')
if __name__ == '__main__':
a = Circle(radius=3, color='red')
b = Circle(radius=a.radius, color=a.color)
print(f'A: radius={a.radius}, color={a.color}')
print(f'B: radius={b.radius}, color={b.color}')
# A: radius=3, color=red
# B: radius=3, color=red
8.6.3. Solution¶

from dataclasses import dataclass
from typing import Self
from abc import ABC, abstractmethod
class Component(ABC):
@abstractmethod
def render(self) -> None: ...
@abstractmethod
def clone(self) -> Self: ...
@dataclass
class Circle(Component):
radius: int | None = None
color: str | None = None
def clone(self) -> Self:
new = Circle()
new.radius = self.radius
new.color = self.color
return new
def render(self) -> None:
print('Rendering circle')
if __name__ == '__main__':
a = Circle(radius=3, color='red')
b = a.clone()
print(f'A: radius={a.radius}, color={a.color}')
print(f'B: radius={b.radius}, color={b.color}')
# A: radius=3, color=red
# B: radius=3, color=red
8.6.4. Use Case - 0x01¶
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal
@dataclass
class User:
firstname: str
lastname: str
username: str
password: str
email: str
last_login: datetime | None
role: Literal['admin', 'user', 'guest']
groups: list[str] = field(default_factory=list)
def clone(self):
return User(
firstname = self.firstname,
lastname = self.lastname,
username = self.username,
password = self.password,
email = self.email,
last_login = self.last_login,
role = self.role,
groups = self.groups)
mark = User(
firstname='Mark',
lastname='Watney',
username='mwatney',
password='Ares3',
email='mwatney@nasa.gov',
last_login=None,
role='admin',
groups=['admins', 'users'],
)
melissa = mark.clone()
print(melissa)
# User(firstname='Mark', lastname='Watney', username='mwatney', password='Ares3', email='mwatney@nasa.gov', last_login=None, role='admin', groups=['admins', 'users'])
melissa.firstname = 'Melissa'
melissa.lastname = 'Lewis'
melissa.username = 'mlewis'
melissa.email = 'mlewis@nasa.gov'
print(melissa)
# User(firstname='Melissa', lastname='Lewis', username='mlewis', password='Ares3', email='mlewis@nasa.gov', last_login=None, role='admin', groups=['admins', 'users'])
8.6.5. Use Case - 0x02¶
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal
@dataclass
class User:
firstname: str
lastname: str
username: str
password: str
email: str
last_login: datetime | None
role: Literal['admin', 'user', 'guest']
groups: list[str] = field(default_factory=list)
def clone(self):
return User(**vars(self))
mark = User(
firstname='Mark',
lastname='Watney',
username='mwatney',
password='Ares3',
email='mwatney@nasa.gov',
last_login=None,
role='admin',
groups=['admins', 'users'],
)
melissa = mark.clone()
print(melissa)
# User(firstname='Mark', lastname='Watney', username='mwatney', password='Ares3', email='mwatney@nasa.gov', last_login=None, role='admin', groups=['admins', 'users'])
melissa.firstname = 'Melissa'
melissa.lastname = 'Lewis'
melissa.username = 'mlewis'
melissa.email = 'mlewis@nasa.gov'
print(melissa)
# User(firstname='Melissa', lastname='Lewis', username='mlewis', password='Ares3', email='mlewis@nasa.gov', last_login=None, role='admin', groups=['admins', 'users'])
8.6.6. Use Case - 0x03¶
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal
@dataclass
class User:
firstname: str
lastname: str
username: str
password: str
email: str
last_login: datetime | None
role: Literal['admin', 'user', 'guest']
groups: list[str] = field(default_factory=list)
def clone(self, **kwargs):
values = vars(self) | kwargs
return User(**values)
mark = User(
firstname='Mark',
lastname='Watney',
username='mwatney',
password='Ares3',
email='mwatney@nasa.gov',
last_login=None,
role='admin',
groups=['admins', 'users'],
)
melissa = mark.clone(
firstname='Melissa',
lastname='Lewis',
username='mlewis',
email='mlewis@nasa.gov',
)
print(melissa)
# User(firstname='Melissa', lastname='Lewis', username='mwatney', password='Ares3', email='mwatney@nasa.gov', last_login=None, role='admin', groups=['admins', 'users'])
8.6.7. Use Case - 0x04¶
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal
@dataclass
class User:
firstname: str
lastname: str
username: str
password: str
email: str
last_login: datetime | None
role: Literal['admin', 'user', 'guest']
groups: list[str] = field(default_factory=list)
def clone(self, **kwargs):
values = vars(self) | kwargs
cls = self.__class__
return cls(**values)
@dataclass
class Admin:
pass
mark = Admin(
firstname='Mark',
lastname='Watney',
username='mwatney',
password='Ares3',
email='mwatney@nasa.gov',
last_login=None,
role='admin',
groups=['admins', 'users'],
)
melissa = mark.clone(
firstname='Melissa',
lastname='Lewis',
username='mlewis',
email='mlewis@nasa.gov',
)
print(melissa)
# Admin(firstname='Melissa', lastname='Lewis', username='mwatney', password='Ares3', email='mwatney@nasa.gov', last_login=None, role='admin', groups=['admins', 'users'])
8.6.8. Assignments¶
"""
* Assignment: DesignPatterns Creational PrototypeDate
* Complexity: easy
* Lines of code: 5 lines
* Time: 3 min
English:
1. Create class `Date` with:
a. `year: int`
b. `month: int`
c. `day: int`
d. method `.clone()`
2. Method `.clone()` returns another `Date` with the same values
3. Run doctests - all must succeed
Polish:
TODO: Polish translation
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from pprint import pprint
>>> date = Date(1969, 7, 21)
>>> result = date.clone()
>>> result.year
1969
>>> result.month
7
>>> result.day
21
"""
from dataclasses import dataclass
@dataclass
class Date:
year: int
month: int
day: int
"""
* Assignment: DesignPatterns Creational PrototypeTime
* Complexity: easy
* Lines of code: 2 lines
* Time: 3 min
English:
1. Create class `Time` with:
a. `hour: int`
b. `minute: int`
c. `second: int`
d. `microsecond: int`
e. method `.clone()`
2. Method `.clone()` returns another `Time` with the same values
3. Use `vars(self)`
4. Run doctests - all must succeed
Polish:
1. Stwórz klasę `Time` z:
a. `hour: int`
b. `minute: int`
c. `second: int`
d. `microsecond: int`
e. metodą `.clone()`
2. Metoda `.clone()` zwraca kolejny `Time` z tymi samymi wartościami
3. Użyj `vars(self)`
4. Uruchom doctesty - wszystkie muszą się powieść
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from pprint import pprint
>>> time = Time(2, 56, 15)
>>> result = time.clone()
>>> result.hour
2
>>> result.minute
56
>>> result.second
15
>>> result.microsecond
0
"""
from dataclasses import dataclass
@dataclass
class Time:
hour: int = 0
minute: int = 0
second: int = 0
microsecond: int = 0
"""
* Assignment: DesignPatterns Creational PrototypeDragon
* Complexity: easy
* Lines of code: 6 lines
* Time: 8 min
English:
1. Create class `Dragon`
2. Dragon has attributes:
a. `name: str`
b. `position: tuple[int,int]` default `(0, 0)`
c. `health: int` random from 50 to 100
d. `gold: int` random from 1 to 100
e. method `.clone()`
3. Method `.clone()` returns another `Dragon` with the same values
4. Use `random.randint()` to generate pseudorandom numbers
5. Run doctests - all must succeed
Polish:
TODO: Polish translation
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from pprint import pprint
>>> from random import seed
>>> seed(0)
>>> dragon = Dragon('Wawelski')
>>> result = dragon.clone()
>>> result.name
'Wawelski'
>>> result.health
74
>>> result.gold
98
>>> result.position
(0, 0)
"""
from dataclasses import dataclass, field
from random import randint
@dataclass
class Dragon:
...
TODO = """
You're building a video editor similar to Adobe Premier.
The editor contains a timeline of various types of components
such as text, clips, audio, and so on. The user should be able
to duplicate any component. The duplicated component should be
added to the timeline. Look at the implementation of the ContextMenu
class in the prototype package of the Exercises project. What are
the problems in the current implementation?Refactor the code using
the prototype pattern. What have you achieved?
"""