ScreenshotNeo

BlogEngineering

Single Responsibility Principle Explained with Examples

Learn how to spot mixed responsibilities, refactor a file manager, and apply SRP without turning every method into its own class.

By the ScreenshotNeo team4 October 20267 min read

The Single Responsibility Principle (SRP) is the “S” in SOLID. Its familiar formulation is: “A class should have only one reason to change.” In practice, group behavior that changes for the same reason and separate behavior that changes for independent reasons. SRP is about a coherent responsibility, not limiting a class to one method. Real Python attributes the quoted formulation to Robert C. Martin.

This guide explains the principle with examples, shows how to identify a mixed responsibility, and walks through a refactoring. The classic wording is class-focused. The same question can also help when drawing boundaries around modules or services, as a broader application of the idea.

1. What is the Single-Responsibility Principle (SRP)?

A class follows SRP when its behavior belongs together because it serves one coherent change area. Ask: Who or what could request a change to this code, and would those requests arise independently? The answer points to the class’s likely reasons to change.

For example, a class that reads and writes files may have a coherent file-access responsibility. If it also compresses and decompresses ZIP archives, archive-format requirements can change independently of ordinary file-access conventions. Those are plausible separate change drivers.

“One reason” does not mean one task, one public method, or one stakeholder in every case. A cohesive class can offer several related operations. Conversely, a small class with only two methods can mix unrelated concerns. Judge the boundary by the cohesion of the behavior and the independence of the changes that affect it.

2. Example: separate file access from ZIP handling

Consider a FileManager that both reads and writes ordinary files and compresses and decompresses ZIP archives. Real Python uses this combination as an SRP counterexample: file access and archive behavior have different policies and can change separately.

Before: two change areas in one class

from pathlib import Path
from zipfile import ZipFile

class FileManager:
    def read_text(self, path: str) -> str:
        return Path(path).read_text(encoding="utf-8")

    def write_text(self, path: str, content: str) -> None:
        Path(path).write_text(content, encoding="utf-8")

    def compress(self, source: str, archive: str) -> None:
        with ZipFile(archive, "w") as zip_file:
            zip_file.write(source, arcname=Path(source).name)

    def extract(self, archive: str, destination: str) -> None:
        with ZipFile(archive, "r") as zip_file:
            zip_file.extractall(destination)

This Python example is runnable as a module or script. The class combines ordinary file operations with ZIP archive operations. A change to text encoding or file access policy and a change to archive layout or extraction policy may both require editing FileManager.

After: cohesive components with an optional coordinator

from pathlib import Path
from zipfile import ZipFile

class TextFileStore:
    def read(self, path: str) -> str:
        return Path(path).read_text(encoding="utf-8")

    def write(self, path: str, content: str) -> None:
        Path(path).write_text(content, encoding="utf-8")

class ZipArchives:
    def compress_file(self, source: str, archive: str) -> None:
        with ZipFile(archive, "w") as zip_file:
            zip_file.write(source, arcname=Path(source).name)

    def extract_all(self, archive: str, destination: str) -> None:
        with ZipFile(archive, "r") as zip_file:
            zip_file.extractall(destination)

class DocumentWorkflow:
    """Optional orchestration when callers need a stable combined operation."""
    def __init__(self, files: TextFileStore, archives: ZipArchives) -> None:
        self.files = files
        self.archives = archives

    def save_and_archive(self, path: str, content: str, archive: str) -> None:
        self.files.write(path, content)
        self.archives.compress_file(path, archive)

if __name__ == "__main__":
    files = TextFileStore()
    archives = ZipArchives()
    files.write("note.txt", "SRP example\n")
    archives.compress_file("note.txt", "note.zip")
    print(files.read("note.txt"))

The extracted classes each own a related set of operations. The coordinator is optional: keep it only when a combined workflow is itself a useful responsibility for callers. This example preserves the basic behavior, but production code should also make error handling, overwrite rules, archive path safety, and file encodings explicit for its requirements.

ZIP extraction deserves particular care: archives can contain paths that escape the intended destination. Do not treat extractall as safe for untrusted archives without validating member paths and applying an appropriate extraction policy. That security concern is separate from the SRP lesson, but it matters when adapting the example.

3. How can the principle help improve object-oriented design?

When unrelated concerns share a class, a change for one concern can affect code owned by another. Separating genuinely independent responsibilities can make ownership clearer, reduce coupling between change areas, and make the likely impact of an edit easier to reason about. These are design aims, not guaranteed or quantified outcomes.

SRP also helps with testing boundaries. A focused component often needs fewer unrelated setup conditions to exercise its behavior. However, splitting a class can add indirection, more interfaces, and more places to navigate. Apply the principle when the boundary makes a real change axis clearer, rather than splitting automatically.

4. A practical method for applying SRP

  1. Name the behavior. Describe the unit’s purpose in a short phrase, such as “read and write text files.” If its description needs several unrelated verbs, investigate further.
  2. Identify change drivers. List the stakeholders, policies, formats, or requirements that could cause edits. Examples include file access conventions, archive format requirements, and business workflow rules.
  3. Check independence. Ask whether those change requests can arise independently and require unrelated edits to the same unit. If not, a split may not help.
  4. Extract a cohesive component. Choose a name that describes the purpose, move the related behavior, and update callers. Keep a coordinator only if it expresses a meaningful workflow.
  5. Review the new boundary. Compare cohesion, coupling, likely ripple effects, and the clarity gained against the indirection introduced. Use the project’s normal checks to preserve behavior.

For two plausible designs, compare how independent their reasons for change are, whether each resulting unit is cohesive, how changes may ripple across boundaries, and whether the new abstraction makes the code easier to understand. Predicting future changes takes judgment; reasonable developers can disagree about the right boundary. Old Dominion University’s SOLID teaching material discusses this tradeoff.

5. More examples and common misconceptions

A module or service with unrelated work

A module that saves user details, processes orders, and ships items groups activities that may serve distinct concerns. Those concerns can change for different reasons, so separate modules or services may clarify ownership. This applies the class-level idea to larger boundaries; it is a generalization, not the literal wording of the classic formulation. The Stack Overflow Blog discusses SRP in the context of modern architecture.

“One responsibility means one method”

No. Responsibility is about a coherent reason to change. A class can contain multiple related methods, such as reading and writing text through the same file-access policy.

“Every noun deserves a class”

No. A class is useful when it gives a meaningful boundary around behavior or a change driver. Creating a class for every concept can add indirection without improving cohesion.

“SRP only applies to classes”

The familiar formulation refers to a class. Developers can apply the same reasoning to modules and services, while recognizing that this extends the original scope.

“Applying SRP always improves code”

No. A split can make a design harder to follow if the concerns are not meaningfully independent or the new boundary contributes only extra indirection. SRP is a design guideline that requires judgment.

6. Or skip the browser setup

If your SRP examples or documentation need a website screenshot, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request can return a PNG, JPEG, WebP, or PDF. See the API documentation for parameters and configuration.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

Replace YOUR_API_KEY with your key. In Node.js, the example uses the built-in fetch and Bun’s file writer; in a Node-only project, write the response bytes with node:fs/promises. Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Sign up free for 1,000 screenshots a month, with no card required.

7. FAQ

Who is associated with the classic SRP wording?

The wording “A class should have only one reason to change” is attributed to Robert C. Martin’s Agile Software Development: Principles, Patterns, and Practices by Real Python.

Is there a measurable percentage improvement from applying SRP?

The sources cited here provide conceptual explanations and examples, not a named statistic quantifying maintenance cost, defect rates, or productivity. Evaluate the principle through the clarity and change isolation of the design at hand.

Should I refactor every class that has several verbs in its name?

Use that as a prompt to inspect the change drivers, not as an automatic rule. If the behaviors evolve together and form a cohesive abstraction, a split may not help.