I'm trying to create a simple simulation with shapes drawn on the screen. I've heard that it's good practice to separate the logic from the rendering, but I'm still not sure how to handle this properly. If the object is not supposed to render itself, where should it be implemented? This thing works, but maybe someone has a better idea. from enum import Enum class ShapeType(Enum): RECTANGLE = 0, CIRCLE = 1, TRIANGLE = 2 class Shape: def __init__(self, s_type: ShapeType): self.s_type = s_type class Rectangle(Shape): def __init__(self, x, y): super().__init__(ShapeType.RECTANGLE) self.x = x self.y = y class Circle(Shape): def __init__(self, r): super().__init__(ShapeType.CIRCLE) self.r = r class Triangle(Shape): def __init__(self, a, b, c): super().__init__(ShapeType.TRIANGLE) self.a = a self.b = b self.c = c class ShapePrinter: def __init__(self): self._type_registry = { ShapeType.RECTANGLE: self.render_rectangle, ShapeType.CIRCLE: self.render_circle, ShapeType.TRIANGLE: self.render_triangle } def render_rectangle(self, surface, shape: Rectangle): print(f"Rendering a rectangle ({shape.x}, {shape.y}) on the {surface}") def render_circle(self, surface, shape: Circle): print(f"Rendering a circle ({shape.r}) on the {surface}") def render_triangle(self, surface, shape: Triangle): print(f"Rendering a circle ({shape.a}, {shape.b}, {shape.c}) on the {surface}") def render(self, surface, shape: Shape): if handler := self._type_registry.get(shape.s_type): handler(surface, shape) else: raise Exception('Shape type not supported') x = ShapePrinter() x.render('surface1', Rectangle(10, 5)) x.render('surface2', Circle(7)) x.render('surface1', Triangle(3, 4, 5)) Continue reading...