Skip to content

Open/Closed Principle ​

Open for extension, closed for modification.

Intent ​

Add new behavior without editing existing, working classes. Prefer new types over endless patches to one filter.

Problem ​

ProductFilter starts simple: filter_by_color, then filter_by_size, then filter_by_size_and_color. Every new criterion or combination means opening that class again. Combinations explode.

Solution ​

Introduce a Specification: an object that answers “does this item match?”
BetterFilter only knows how to apply a spec. New rules are new classes.

Structure ​

PieceRole
ProductName, color, size
ProductFilterOld approach (violation)
SpecificationMatch rule (is_satisfied)
ColorSpecification / SizeSpecificationConcrete rules
AndSpecificationCombine rules
BetterFilterApply any spec

Compare code ​

Violation — methods grow on ProductFilter

python
class ProductFilter:
    def filter_by_color(self, products, color): ...
    def filter_by_size(self, products, size): ...
    def filter_by_size_and_color(self, products, size, color): ...
    # next: weight, size+weight, color+weight, ...

Compliant — this repo

python
class ColorSpecification(Specification):
    def is_satisfied(self, item):
        return item.color == self.color

class BetterFilter(Filter):
    def filter(self, items, spec):
        return [item for item in items if spec.is_satisfied(item)]

# compose without touching BetterFilter
large_blue = AndSpecification(
    SizeSpecification(Size.LARGE),
    ColorSpecification(Color.BLUE),
)

Walkthrough ​

Quick check ​

Which change follows OCP for a new “filter by price” need?

Run locally ​

bash
uv run ocp

Source: main.py · tutorial README

Takeaway ​

  • Don’t grow one filter with every criterion
  • Encode criteria as composable objects
  • Extend by adding classes; keep the engine closed