cconnected.
Understand the pattern. Find your own way.Find your starting point
Your learning feed/Idea 161 of 218
Ideas this builds on
Low-level design161 / 218

Keep changes from spreading everywhere

Picture

A report’s wording and its storage destination change independently. Let one function render the report and a small writer object save it. Callers need only a truthful save promise, not every possible storage capability.

Predict

What would make a read-only storage object an invalid replacement for this writer?

Walk through the small example

The service relies on save accepting valid data. An object that always rejects writes would strengthen the requirements or remove promised behavior, breaking substitution.

Mental model and complete Python solution

Single responsibility separates rendering from persistence. Open–closed design lets a new writer implement the existing contract without rewriting ReportService. Liskov substitution requires replacements to preserve the caller’s behavioral assumptions. Interface segregation gives this service only save, not unrelated read/delete methods. Dependency inversion makes the service depend on that small writer contract; constructor injection supplies the chosen detail. These are review questions, not a mandate to abstract every class.

What the function promises: ReportService.publish accepts a name and iterable of lines. It joins the lines and delegates one save. The in-memory writer replaces an existing report with the same name; storage exceptions propagate.

from typing import Protocol

class Writer(Protocol):
    def save(self, name: str, content: str) -> None: ...

def render_report(lines):
    return "\n".join(lines)

class MemoryWriter:
    def __init__(self):
        self.reports = {}

    def save(self, name: str, content: str) -> None:
        self.reports[name] = content

class ReportService:
    def __init__(self, writer: Writer):
        self.writer = writer

    def publish(self, name, lines):
        self.writer.save(name, render_report(lines))

store = MemoryWriter()
service = ReportService(store)
service.publish("day", ["A", "B"])
assert store.reports["day"] == "A\nB"
service.publish("day", ["C"])
assert store.reports == {"day": "C"}

Work and storage: O(total text length) rendering and stored text space. The small abstraction should be justified by genuine alternate storage or test needs.

When to recognize it: Separate reasons to change, then depend on small promises that replacements can actually keep.

Check before applying it: Do not manufacture an interface for every line of code. More layers can hide a simple design without buying useful flexibility.

Change one rule

There will only ever be one tiny in-memory use with no alternate behavior. Must we keep all these layers?

See what changes

No. A function and dictionary may be clearer. SOLID helps diagnose painful coupling and broken contracts; it does not award points for extra interfaces.

Connect

Agree on a small promise: Small contracts support substitution and focused dependencies.

Swap the rule, not the whole feature: A changing rule can be one replaceable strategy.