samedi 4 novembre 2023

Pipeline of Chain Of responsibility pattern

I am studying the classical Chain Of Responsibility GoF design pattern.

I think I am able to understand the classic pattern "as is" and as intended, i.e. as a chain of request handlers of which one and just one will actually process the request.

It came to my mind that almost the same exact pattern could be used in a context where all handlers work together and collaborate to fulfill the request, as in a "pipeline".

The use case is when each step performs a different part of the global task, and maybe one step relies on the result of the previous one(s), i.e. depends on the whole work done "so far".

This is somewhat similar in scope but different in shape to method chaining.

In order to clarify better, I have made an example.

Let's suppose to build an html tag.

Three steps (in my simplified case) are involved:

  • Definition of the image source

  • Definition of alternate text

  • Definition of the style

I feel like the same structure of the chain of responsibility could be used to implement the pipeline.

Here is a working prototype if the idea:

from __future__ import annotations
from abc import ABC, abstractmethod

from typing import Optional


class HtmlImageTag:
    def __init__(self) -> None:
        self.src = None
        self.alt = None
        self.style = None

    def render(self) -> str:
        return f"<img src=\"{self.src}\" style=\"{self.style}\", alt=\"{self.alt}\" />"


class HtmlImageTagBuildingStep(ABC):
    def __init__(self) -> None:
        super().__init__()
        self.next = None

    def set_next(self, next: Optional[HtmlImageTagBuildingStep]):
        self.next = next

    @abstractmethod
    def perform(self, image: HtmlImageTag) -> HtmlImageTag:
        ...


class SourceStep(HtmlImageTagBuildingStep):
    def perform(self, image: HtmlImageTag) -> HtmlImageTag:
        image.src = "images/my-image.jpg"

        return self.next.perform(image)


class AltStep(HtmlImageTagBuildingStep):
    def perform(self, image: HtmlImageTag) -> HtmlImageTag:
        image.alt = "This is an image"

        return self.next.perform(image)


class StyleStep(HtmlImageTagBuildingStep):
    def perform(self, image: HtmlImageTag) -> HtmlImageTag:
        image.style = "width: 300px; height:200px"

        return self.next.perform(image)


class FinalStep(HtmlImageTagBuildingStep):
    def perform(self, image: HtmlImageTag) -> HtmlImageTag:
        return image


def build_image_tag() -> HtmlImageTag:
    source = SourceStep()
    alt = AltStep()
    style = StyleStep()
    final = FinalStep()

    source.set_next(alt)
    alt.set_next(style)
    style.set_next(final)

    return source.perform(HtmlImageTag())


if __name__ == "__main__":
    image_tag: HtmlImageTag = build_image_tag()

    print(image_tag.render())

    # Result:
    # <img src="images/my-image.jpg" style="width: 300px; height:200px", alt="This is an image" />

My questions - hopefully clarified by the example above - are the following:

  • Is this (almost exact) way of chaining steps reasonably still a "chain of responsibility"?
  • Does this "pattern" (or, put simply, "way of doing things") have a special name and/or did someone else study it in some similar form?

Aucun commentaire:

Enregistrer un commentaire