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.