Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Low-Level Design (Object-Oriented Design) Module 4 – Design principles: SOLID and beyond

Single Responsibility Principle (SRP)

The Single Responsibility Principle as one reason to change per class: find who asks for changes, split a bill class by team, and stop before fragments.

  • Intermediate
  • 20 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7 and Node.js 24.21.0
  • By MySmartCoPilot

What you will learn

  • Define the Single Responsibility Principle in terms of reasons to change
  • Identify who asks for each change before deciding where code belongs
  • Split a class that serves several groups of people into cohesive parts
  • Avoid over-splitting a design into fragments with no behaviour of their own

Before you start

On this page

The previous lesson measured a design by the changes it meets. The Single Responsibility Principle (SRP) turns that into a rule for one class: look at who will ask for its changes. If two different groups of people will, the class has two responsibilities, and a change made for one group can break what the other one relies on.

What the principle says

Robert C. Martin lists SRP first among five principles of class design, which are now known together by their initials as SOLID. He traces the set to an article he posted to the comp.object newsgroup in 1995. His one-line version is:

A class should have one, and only one, reason to change.

The hard part is the phrase “reason to change”. In a later essay Martin answers it: reasons to change are people. A class should answer to one group of people with one narrowly defined business function, so that a change requested by that group cannot break something another group depends on. He gives the principle a second wording, too:

Gather together the things that change for the same reasons. Separate those things that change for different reasons.

That is cohesion and coupling again, stated in terms of change requests: keep together what changes together, and keep apart what changes for different reasons. Martin describes SRP as his attempt, in the late 1990s, to pull Parnas’s criterion, the separation of concerns, coupling and cohesion into one principle.

Finding the reasons to change

Before you split anything, list the class’s public methods and ask of each one: who would ask for this to change, and whose work breaks if it goes wrong? Group the methods by the answer. One group means one responsibility.

Take an electricity bill. The tariff team decides the prices: a fixed charge, then slabs of units at rising prices. The customer-letters team decides what the printed bill says. The records team decides the format of the archive file that their reconciliation tools read. Three groups, three reasons to change, and here they all sit in one class.

A change for one team that breaks two others

ElectricityBill has one method per team, and all three call a shared helper, _units(). Then the tariff team asks for the first 50 units of every month to be free. The quickest edit is in _units(), because amount() gets its units from there, and that is what bill_v2.py does. Each team keeps one check of its own:

One class, three teams, one quick change

before/team_checks.py

"""Each team's own check, run against the bill before and after the tariff team's change."""
import bill_v1
import bill_v2


def checks(module, expected_amount):
    bill = module.ElectricityBill(account=4417, previous=4120, current=4330, exported=30)
    units_line, row = bill.letter().splitlines()[1], bill.archive_row()
    return [
        ("tariff team", f"amount is {expected_amount}", bill.amount() == expected_amount, bill.amount()),
        ("letters team", "letter shows 180 units used", units_line == "Units used: 180", units_line),
        ("records team", "archive row has 180 units", row.split(",")[1] == "180", row),
    ]


for module, expected, title in [
    (bill_v1, 750, "bill_v1.py, before the change"),
    (bill_v2, 500, "bill_v2.py, after the tariff team's change"),
]:
    print(title)
    for team, check, ok, seen in checks(module, expected):
        print(f"  {team:<13} {check:<28} {'ok' if ok else f'FAILED, got {seen!r}'}")

Output

bill_v1.py, before the change
  tariff team   amount is 750                ok
  letters team  letter shows 180 units used  ok
  records team  archive row has 180 units    ok
bill_v2.py, after the tariff team's change
  tariff team   amount is 500                ok
  letters team  letter shows 180 units used  FAILED, got 'Units used: 130'
  records team  archive row has 180 units    FAILED, got '4417,130,500'

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 team_checks.py

before/bill_v1.py

"""An electricity bill that three teams change, each for its own reasons. The version before any change."""


class ElectricityBill:
    FIXED_CHARGE = 50
    SLABS = [(100, 3), (100, 5), (None, 8)]  # (units in the slab, price per unit); None: all the rest

    def __init__(self, account, previous, current, exported):
        self.account = account
        self.previous = previous
        self.current = current
        self.exported = exported  # units that rooftop solar sent back to the grid

    def _units(self):
        return self.current - self.previous - self.exported

    def amount(self):  # the tariff team asks for changes here
        left, total = self._units(), self.FIXED_CHARGE
        for size, price in self.SLABS:
            used = left if size is None else min(left, size)
            total += used * price
            left -= used
        return total

    def letter(self):  # the customer-letters team asks for changes here
        return f"Account {self.account}\nUnits used: {self._units()}\nAmount due: {self.amount()}"

    def archive_row(self):  # the records team asks for changes here
        return f"{self.account},{self._units()},{self.amount()}"

before/bill_v2.py

"""The same bill after the tariff team's request: the first 50 units of every month are free."""


class ElectricityBill:
    FIXED_CHARGE = 50
    SLABS = [(100, 3), (100, 5), (None, 8)]  # (units in the slab, price per unit); None: all the rest

    def __init__(self, account, previous, current, exported):
        self.account = account
        self.previous = previous
        self.current = current
        self.exported = exported  # units that rooftop solar sent back to the grid

    def _units(self):
        # The tariff change, made where amount() gets its units from.
        return max(self.current - self.previous - self.exported - 50, 0)

    def amount(self):  # the tariff team asks for changes here
        left, total = self._units(), self.FIXED_CHARGE
        for size, price in self.SLABS:
            used = left if size is None else min(left, size)
            total += used * price
            left -= used
        return total

    def letter(self):  # the customer-letters team asks for changes here
        return f"Account {self.account}\nUnits used: {self._units()}\nAmount due: {self.amount()}"

    def archive_row(self):  # the records team asks for changes here
        return f"{self.account},{self._units()},{self.amount()}"

The tariff team’s check passes: 180 units now cost 500 instead of 750. The other two teams never asked for anything, and both of their checks fail, because the letter and the archive row now say 130 units instead of the 180 that the meter counted. Nobody wrote a bug in the letters code or the records code. The class put three teams’ concerns behind one helper, so the change for one team reached the other two.

Common mistake

A shared private helper looks like good reuse, but _units() meant three things: the units to charge for, the units to print and the units to archive. They happened to be equal until the tariff changed. When code serves several groups of people, check whether it means the same thing to each of them before you share it.

Splitting by reason to change

Split the class so that each team’s changes land in one place:

  • tariff.py belongs to the tariff team: Tariff.amount(units) knows the fixed charge, the slabs and, now, the free units.
  • letter.py belongs to the customer-letters team and archive.py to the records team.
  • meter.py holds Reading, a frozen data class that records what the meter counted. It is data that all three use, not a fourth responsibility tangled with theirs.
One module per reason to change

after/team_checks.py

"""The same three checks, with the tariff team's change made inside the tariff."""
from archive import archive_row
from letter import letter
from meter import Reading
from tariff import Tariff

reading = Reading(account=4417, previous=4120, current=4330, exported=30)
for tariff, expected, title in [
    (Tariff(), 750, "Tariff(), before the change"),
    (Tariff(free_units=50), 500, "Tariff(free_units=50), after the tariff team's change"),
]:
    amount = tariff.amount(reading.units())
    units_line, row = letter(reading, amount).splitlines()[1], archive_row(reading, amount)
    print(title)
    for team, check, ok, seen in [
        ("tariff team", f"amount is {expected}", amount == expected, amount),
        ("letters team", "letter shows 180 units used", units_line == "Units used: 180", units_line),
        ("records team", "archive row has 180 units", row.split(",")[1] == "180", row),
    ]:
        print(f"  {team:<13} {check:<28} {'ok' if ok else f'FAILED, got {seen!r}'}")

Output

Tariff(), before the change
  tariff team   amount is 750                ok
  letters team  letter shows 180 units used  ok
  records team  archive row has 180 units    ok
Tariff(free_units=50), after the tariff team's change
  tariff team   amount is 500                ok
  letters team  letter shows 180 units used  ok
  records team  archive row has 180 units    ok

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 team_checks.py

after/tariff.py

"""The tariff team's rules: what a number of units costs. Only tariff changes touch this file."""


class Tariff:
    def __init__(self, fixed_charge=50, slabs=((100, 3), (100, 5), (None, 8)), free_units=0):
        self.fixed_charge = fixed_charge
        self.slabs = slabs  # (units in the slab, price per unit); None: all the rest
        self.free_units = free_units

    def amount(self, units):
        left, total = max(units - self.free_units, 0), self.fixed_charge
        for size, price in self.slabs:
            used = left if size is None else min(left, size)
            total += used * price
            left -= used
        return total

after/meter.py

"""A meter reading: what the meter counted. It changes only when the meter data changes."""
from dataclasses import dataclass


@dataclass(frozen=True)
class Reading:
    account: int
    previous: int
    current: int
    exported: int  # units that rooftop solar sent back to the grid

    def units(self):
        return self.current - self.previous - self.exported

after/letter.py

"""The customer-letters team's layout."""


def letter(reading, amount):
    return f"Account {reading.account}\nUnits used: {reading.units()}\nAmount due: {amount}"

after/archive.py

"""The records team's archive format."""


def archive_row(reading, amount):
    return f"{reading.account},{reading.units()},{amount}"

The same request is now Tariff(free_units=50), a change inside the tariff team’s file, and all three checks pass. The letter still shows the 180 metered units because it never asks the tariff for anything but an amount.

Before: three teams ask for changes to one ElectricityBill class. After: each team has one module, and two of them read meter.py.Before: one class, three teamsAfter: one module per reason to changeTariffteamCustomer-letters teamRecordsteamElectricityBillamount() letter() archive_row()all three call _units()TariffteamCustomer-letters teamRecordsteamtariff.pyTariffletter.pyletter()archive.pyarchive_row()meter.pyReadingasks for changesasks for changesasks for changesreadsreads

Who asks for changes, before and after the split

Text description of the diagram

The diagram has two parts, one above the other.

  1. Before: the tariff team, the customer-letters team and the records team all ask for changes to one class, ElectricityBill, whose methods amount(), letter() and archive_row() all call the same helper, _units().
  2. After: the tariff team asks for changes to tariff.py (Tariff.amount(units)), the customer-letters team to letter.py (letter(reading, amount)) and the records team to archive.py (archive_row(reading, amount)). letter.py and archive.py read the metered units from meter.py (Reading.units()), which none of the three teams' changes need to touch.

Two details of this split are worth noticing. letter.py and archive.py are plain functions: SRP is about the reasons a module changes, and a function in a module of its own satisfies it as well as a class does. And the code that puts the parts together, team_checks.py here, is the one place that knows about all of them; in a real program it is the place where the program starts.

How far to split

If one responsibility per class is good, is one method per class better? Here are the tariff and the letter again, cut into eight classes with a method or two each. They print the same letter. This program measures both versions with Python’s ast module and follows one letter through each with sys.setprofile, which calls a function of yours on every call and return:

Three parts against eight classes

oversplit/measure.py

"""Compares the three-part design (../after) with the eight-class one (parts.py) on the same job: one letter."""
import ast
import os
import sys
from pathlib import Path

HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE.parent / "after"))
sys.dont_write_bytecode = True

import parts
from letter import letter
from meter import Reading
from tariff import Tariff


def size(files):
    """(classes, functions and methods, non-blank lines) of some source files."""
    classes = functions = lines = 0
    for file in files:
        source = file.read_text()
        lines += sum(1 for line in source.splitlines() if line.strip())
        for node in ast.walk(ast.parse(source)):
            classes += isinstance(node, ast.ClassDef)
            functions += isinstance(node, ast.FunctionDef)
    return classes, functions, lines


def deepest_chain(job, files):
    """The most calls into `files` that are open at the same moment while `job` runs."""
    names = {os.path.realpath(f) for f in files}
    depth = deepest = 0

    def watch(frame, event, arg):
        nonlocal depth, deepest
        if os.path.realpath(frame.f_code.co_filename) in names:
            if event == "call":
                depth += 1
                deepest = max(deepest, depth)
            elif event == "return":
                depth -= 1

    sys.setprofile(watch)
    try:
        result = job()
    finally:
        sys.setprofile(None)
    return deepest, result


AFTER = HERE.parent / "after"
reading = Reading(account=4417, previous=4120, current=4330, exported=30)
designs = [
    ("three parts: tariff.py, letter.py", [AFTER / "tariff.py", AFTER / "letter.py"],
     lambda: letter(reading, Tariff().amount(reading.units()))),
    ("eight classes: parts.py", [HERE / "parts.py"], lambda: parts.build().letter(reading)),
]
shared = AFTER / "meter.py"
letters = set()
print(f"{'design':<36}{'classes':>8}{'functions':>11}{'lines':>7}{'deepest chain':>15}")
for label, files, job in designs:
    classes, functions, lines = size(files)
    depth, text = deepest_chain(job, [*files, shared])
    letters.add(text)
    print(f"{label:<36}{classes:>8}{functions:>11}{lines:>7}{depth:>15}")
print(f"\nBoth print the same letter: {'yes' if len(letters) == 1 else 'NO'}")
print(letters.pop())

Output

design                               classes  functions  lines  deepest chain
three parts: tariff.py, letter.py          1          3     16              2
eight classes: parts.py                    8         13     43              4

Both print the same letter: yes
Account 4417
Units used: 180
Amount due: 750

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 measure.py

oversplit/parts.py

"""The tariff and the letter again, split into a class per small step: eight classes, most with one method."""


class SlabTable:
    def rows(self):
        return ((100, 3), (100, 5), (None, 8))


class FixedCharge:
    def value(self):
        return 50


class FreeUnits:
    def __init__(self, count=0):
        self.count = count

    def apply(self, units):
        return max(units - self.count, 0)


class SlabWalker:
    def __init__(self, table):
        self.table = table

    def charge(self, units):
        left, total = units, 0
        for size, price in self.table.rows():
            used = left if size is None else min(left, size)
            total += used * price
            left -= used
        return total


class AmountCalculator:
    def __init__(self, fixed, free, walker):
        self.fixed, self.free, self.walker = fixed, free, walker

    def amount(self, units):
        return self.fixed.value() + self.walker.charge(self.free.apply(units))


class UnitsLine:
    def text(self, reading):
        return f"Units used: {reading.units()}"


class AmountLine:
    def text(self, amount):
        return f"Amount due: {amount}"


class LetterAssembler:
    def __init__(self, calculator, units_line, amount_line):
        self.calculator, self.units_line, self.amount_line = calculator, units_line, amount_line

    def letter(self, reading):
        amount = self.calculator.amount(reading.units())
        lines = [f"Account {reading.account}", self.units_line.text(reading), self.amount_line.text(amount)]
        return "\n".join(lines)


def build():
    calculator = AmountCalculator(FixedCharge(), FreeUnits(), SlabWalker(SlabTable()))
    return LetterAssembler(calculator, UnitsLine(), AmountLine())

The eight-class version has more than twice the lines, and printing one letter goes four calls deep instead of two. Worse, it has not separated anything that changes for different reasons. SlabTable, FixedCharge, FreeUnits, SlabWalker and AmountCalculator all answer to the tariff team, so the tariff can no longer be read in one place: each tariff request now starts with working out which of five classes it touches, and a new kind of rule may have to edit several of them. That is the opposite failure: one responsibility spread over many classes, where SRP wanted one class per responsibility.

One method per class is not SRP

SRP counts reasons to change, not methods. A class with six methods that the tariff team alone ever asks about has one responsibility. Split when two groups of people would ask for changes, or when a class’s tests need set-ups for unrelated features. Keep things together when they always change together, even if that makes a class longer.

SRP beyond classes

The same question works at every size:

  • Functions: a function that parses, validates and saves does three jobs, and splitting it makes each easier to read and test. SRP’s own question, who asks for the change, matters most at the level of modules and classes.
  • Packages: among Martin’s package principles is the Common Closure Principle, “Classes that change together are packaged together”, which is SRP one level up.
  • Services: Martin points out that splitting a system into small services does not solve the problem by itself. A service can bundle code that changes for different reasons just as a class can.

In a design interview

When you propose classes, say whose rules each one holds: “the pricing rules belong to the business, the receipt layout to the product team, the archive format to whoever runs the reconciliation”. Naming the people makes your split easy to defend, and when the interviewer adds a requirement you can say at once which class it lands in.

Python Online Compiler Paste the before files into the runner and try other change requests, such as a new slab price.

Key takeaways

  • SRP: a class (or module) should have one reason to change, and reasons to change are the groups of people who ask for changes.
  • Find responsibilities by asking who would request a change to each method; group the methods by the answer.
  • Shared helpers between groups are where one group’s change breaks another’s work; split them along the groups.
  • Do not split further than the reasons to change: eight one-method classes for one team’s rules spread one responsibility over many files.

Exercise

Exercise · Medium · Python

Split a report card that serves two groups of people

ReportCard in report_card.py has two reasons to change. The exam board sets the grade boundaries, and the school office decides what the printed card looks like. Today both kinds of request edit the same method, so a new card layout can break the grading and new boundaries can break the card. Split the class so that each group's changes land in a class of its own:

  • GradeRules().grade(mark) returns the grade for a mark out of 100: "A" from 90, "B" from 75, "C" from 60, "D" from 40 and "F" below 40.
  • CardLayout().render(name, rows, average) returns the printed card. rows is a list of (subject, mark, grade) and average a number; the layout does no grading of its own.
  • ReportCard(name, marks, rules=None, layout=None) uses a GradeRules() and a CardLayout() when none are given. rows() returns the (subject, mark, grade) triples in the order of marks, average() the mean mark, and text() the card that the layout renders, exactly as text() prints it today.

For ReportCard("Ravi", {"Maths": 75, "History": 60}), text() returns:

Ravi
  Maths       75  B
  History     60  C
  Average    67.5

The sample tests check the cards of six students, every grade boundary, and pass in rules and a layout of their own to show that each can change without the other.

Starter code · report_card.py

class ReportCard:
    """Grades a student's marks and lays out the printed card. The exam board and the school office both edit it."""

    def __init__(self, name, marks):
        self.name = name
        self.marks = marks  # {subject: mark out of 100}, in the order the card lists them

    def text(self):
        lines = [self.name]
        for subject, mark in self.marks.items():
            if mark >= 90:
                grade = "A"
            elif mark >= 75:
                grade = "B"
            elif mark >= 60:
                grade = "C"
            elif mark >= 40:
                grade = "D"
            else:
                grade = "F"
            lines.append(f"  {subject:<10}{mark:>4}  {grade}")
        average = sum(self.marks.values()) / len(self.marks)
        lines.append(f"  Average   {average:>5.1f}")
        return "\n".join(lines)
The sample tests · test_report_card.py
from report_card import CardLayout, GradeRules, ReportCard

STUDENTS = [
    ("Asha", {"Maths": 92, "Physics": 74}, "Asha\n  Maths       92  A\n  Physics     74  C\n  Average    83.0"),
    ("Ravi", {"Maths": 75, "History": 60}, "Ravi\n  Maths       75  B\n  History     60  C\n  Average    67.5"),
    ("Meena", {"Biology": 39, "Art": 40}, "Meena\n  Biology     39  F\n  Art         40  D\n  Average    39.5"),
    ("Kabir", {"Music": 100}, "Kabir\n  Music      100  A\n  Average   100.0"),
    ("Zoya", {"Maths": 89, "Physics": 90, "Art": 59}, "Zoya\n  Maths       89  B\n  Physics     90  A\n  Art         59  D\n  Average    79.3"),
    ("Ira", {"History": 0, "Maths": 61}, "Ira\n  History      0  F\n  Maths       61  C\n  Average    30.5"),
]


class PassFail:
    def grade(self, mark):
        return "P" if mark >= 40 else "F"


class CsvLayout:
    def render(self, name, rows, average):
        return "\n".join(f"{name},{subject},{mark},{grade}" for subject, mark, grade in rows)


def test_cards_unchanged():
    """prints the same cards as before for six students"""
    for name, marks, expected in STUDENTS:
        assert ReportCard(name, marks).text() == expected


def test_grade_boundaries():
    """GradeRules keeps the exam board's boundaries"""
    rules = GradeRules()
    marks = [100, 90, 89, 75, 74, 60, 59, 40, 39, 0]
    assert [rules.grade(m) for m in marks] == ["A", "A", "B", "B", "C", "C", "D", "D", "F", "F"]


def test_rules_can_change_alone():
    """new grading rules change the grades, not the layout"""
    card = ReportCard("Meena", {"Biology": 39, "Art": 40}, rules=PassFail())
    assert card.text() == "Meena\n  Biology     39  F\n  Art         40  P\n  Average    39.5"


def test_layout_can_change_alone():
    """a new layout uses the same grades"""
    card = ReportCard("Ravi", {"Maths": 75, "History": 60}, layout=CsvLayout())
    assert card.text() == "Ravi,Maths,75,B\nRavi,History,60,C"


def test_layout_needs_no_rules():
    """CardLayout lays out rows it is given, with no grading inside"""
    assert CardLayout().render("Kabir", [("Music", 100, "X")], 100.0) == "Kabir\n  Music      100  X\n  Average   100.0"


def test_rows_and_average():
    """rows() pairs each mark with its grade; average() is the mean mark"""
    card = ReportCard("Zoya", {"Maths": 89, "Art": 59})
    assert card.rows() == [("Maths", 89, "B"), ("Art", 59, "D")]
    assert card.average() == 74.0
A hint

Move the if/elif chain into GradeRules.grade() and the f-strings into CardLayout.render(). Then ReportCard.rows() becomes one list comprehension that calls self.rules.grade(mark), and text() hands self.rows() and self.average() to self.layout.render().

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 6 In the Single Responsibility Principle, what is a "reason to change"?

    Choose one answer.

    Show the answer to question 1

    Answer: A group of people with one business function who may ask for the class to change

    Martin's answer is that reasons to change are people. Bug fixes and refactorings are the programmer's work, not requests from the people the code serves. A class has one responsibility when one group of people, with one business function, is the source of its change requests.

  2. Question 2 of 6 The comments say who decides how each method behaves. How many responsibilities does Ticket have?

    Read the code, then choose one answer.

    class Ticket:
        def price(self): ...          # fare rules of the transport authority
        def refund_amount(self): ...  # fare rules of the transport authority
        def receipt_text(self): ...   # wording owned by customer support
    Show the answer to question 2

    Answer: Two: the fare rules and the receipt wording

    price() and refund_amount() change when the transport authority changes its fare rules, so they belong together. receipt_text() changes when customer support rewords the receipt. Two groups ask for changes, so the class has two responsibilities, whatever the number of methods.

  3. Question 3 of 6 In the bill example, the tariff team's change made the letters team's check fail. Why?

    Choose one answer.

    Show the answer to question 3

    Answer: All three teams' methods used one helper, _units(), and the tariff change was made inside it

    _units() meant the units to charge for, to print and to archive. Those were equal until the tariff team asked for 50 free units. Editing the shared helper changed what all three methods used, although only one team asked.

  4. Question 4 of 6 In the eight-class version, five classes hold the tariff team's rules. What is the problem?

    Choose one answer.

    Show the answer to question 4

    Answer: One responsibility is spread over many classes, so a change request means reading several of them and often editing more than one

    SRP asks for one class per reason to change, not for many classes per reason. With the tariff rules spread over five classes, they can no longer be read in one place, and one change request may have to touch several of them.

  5. Question 5 of 6 Which of these suggest that a class has more than one responsibility?

    Choose every answer that is right.

    Show the answer to question 5

    Answer:

    • Its tests need set-ups for features that have nothing to do with each other
    • A change made for one feature broke a test of another feature
    • Two different groups of people ask for changes to it

    The number of methods and the length of the name say nothing about reasons to change. Different groups asking for changes, changes that break unrelated features and tests that need unrelated set-ups all point to code that serves more than one reason to change.

  6. Question 6 of 6 Which of Martin's package principles applies the idea of SRP to packages?

    Choose one answer.

    Show the answer to question 6

    Answer: The Common Closure Principle: classes that change together are packaged together

    The Common Closure Principle groups classes by the reasons they change, as SRP does for the parts of a class. The other three are about dependencies between packages and how packages are released.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.