Swap renderers at runtime

Step 4 - Swap renderers at runtime

Because the Shape only holds a reference to its Renderer, we can replace that reference while the program runs. The same shape object then produces output from a different backend - no new objects, no subclassing.

Implement Shape.set_renderer(renderer) so it stores the new renderer on self. After calling it, draw() must use the new backend on the very same shape.

Hints
Starter code
class Renderer:
    def render_circle(self, radius):
        raise NotImplementedError
    def render_square(self, side):
        raise NotImplementedError

class VectorRenderer(Renderer):
    def render_circle(self, radius):
        return "vector circle r=" + str(radius)
    def render_square(self, side):
        return "vector square s=" + str(side)

class RasterRenderer(Renderer):
    def render_circle(self, radius):
        return "raster pixels for circle r=" + str(radius)
    def render_square(self, side):
        return "raster pixels for square s=" + str(side)

class Shape:
    def __init__(self, renderer):
        self.renderer = renderer
    def set_renderer(self, renderer):
        """Swap the implementation (renderer) used by this shape at runtime."""
        # TODO: store the new renderer on self so later draw() calls use it
        pass
    def draw(self):
        raise NotImplementedError

class Circle(Shape):
    def __init__(self, renderer, radius):
        super().__init__(renderer)
        self.radius = radius
    def draw(self):
        return self.renderer.render_circle(self.radius)

class Square(Shape):
    def __init__(self, renderer, side):
        super().__init__(renderer)
        self.side = side
    def draw(self):
        return self.renderer.render_square(self.side)
Write it on a laptop

The editor and the test runner need a wide screen. Open this page on a desktop browser and your progress will be waiting.