Chain Of responsibility pattern used as "pipeline"

59 views Asked by At

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?
1

There are 1 answers

0
jaco0646 On

In the original, Gang of Four Design Patterns, heavy emphasis is placed on the intent of a pattern. So the exact same syntax applied for different reasons in different context may be considered different patterns. From this perspective a Chain of Responsibility is definitely not a pipeline. This perspective is useful if you consider design patterns as primarily a tool for communication, i.e. a shared vocabulary among programmers. In that case conveying intent of a pattern is as important as syntax. However, you can also make the argument that intent is purely academic and doesn't change the structure or functionality or inherent nature of a pattern.

In the example here, consider whether pipe-and-filter might be an appropriate description.