0 XP · 0 scenes

Open / Closed

Open for extension, closed for modification
The problem

A price calculator has an if/elif for every kind of discount.

The idea

Put the changing part behind an interface.

Reach for it when

A switch or if/elif grows each time a new type appears

1

The picture from life

A power strip: when you buy a new gadget you plug it in. You do not break the wall and rewire the house. The strip is "closed" (never opened up) yet "open" (any new plug fits).

2

The problem

What goes wrong when you do not use it.

A price calculator has an if/elif for every kind of discount. Each new offer means editing the same function, retesting every old branch and risking a typo in code that already worked.

✗ Before
Marketingfinal_price()if kind == 'festival': ...elif kind == 'member': ...elif kind == 'student': ... ← new# edit again for the next offernew offer = edit
  • calls / uses
Every new offer means opening and editing the old, tested function.
Reach for it when
  • A switch or if/elif grows each time a new type appears
  • You keep editing tested code to add a feature
  • Several teams add variants of the same behaviour
3

The idea

How the pattern fixes it.

Put the changing part behind an interface. The calculator talks only to the interface; a new offer is a new class that implements it. Nothing old is touched.

✓ After
checkout()«interface»Discountapply(amount)FestivalDiscountMemberDiscountStudentDiscount← added laterapply()
  • implements
  • calls / uses
A new offer is a new class. Existing code is never opened.

Who is who

Discount (interface)
The promise: apply(amount) returns the new price.
FestivalDiscount, MemberDiscount...
Each offer is one small class. Adding one never edits the others.
checkout
The closed code. It just calls discount.apply and never changes.

Build it in steps

  1. 1Find the thing that keeps changing (the type of discount).
  2. 2Define a small interface for it.
  3. 3Move each branch into its own class.
  4. 4Make the caller depend only on the interface.
4

Watch it run

Step through the calls. Tap an arrow to jump to it.

ClientcheckoutStudentDiscount1checkout(1000, StudentDiscount())2apply(1000)3800.04800.0
Step 1/4

Marketing added StudentDiscount. checkout() was not edited at all.

checkout(1000, StudentDiscount())
5

The code

Python and Java. Try writing the classes yourself from the diagram first.

class Discount:
    def apply(self, amount):
        return amount


class FestivalDiscount(Discount):
    def apply(self, amount):
        return amount * 0.9


class MemberDiscount(Discount):
    def apply(self, amount):
        return amount - 50 if amount > 500 else amount


class StudentDiscount(Discount):               # added later, nothing else edited
    def apply(self, amount):
        return amount * 0.8


def checkout(amount, discount):
    return discount.apply(amount)              # closed for modification


def demo():
    return checkout(1000, FestivalDiscount()), checkout(1000, MemberDiscount()), checkout(1000, StudentDiscount())

What you gain

  • New behaviour arrives as new code, not edits to old code
  • Old features cannot be broken by a new one

What it costs

  • You must guess the right place for the interface; guess wrong and it is clutter
  • Do not add it before a second variant exists

You have already met it in

  • Plugins in editors and browsers
  • Sort with a comparator you pass in

Often mixed up with

  • StrategyStrategy is the pattern that most often delivers Open/Closed. OCP is the goal, Strategy is one way to get there.
6

Make it stick

Write a shipping-cost function with three methods using if/else, then refactor so adding a fourth method needs no edit to old code.

Confidence