ArchitectMind
  1. Home
  2. Tutorials
  3. Book 5 · Software Design & Design Patterns
  4. Volume II
  5. Classes
Book 5 · Volume II — Object-Oriented Design & SOLID · Topic 1.2

Classes

Responsibility, collaboration and inheritance

9 min readStage 2 · Frameworks & design8 interview questions
Chapter
Chapter 1 — Object-Oriented Design
Prerequisites
Topic 1.1
You will be able to
Design classes from responsibilities, model collaborations between objects, assign responsibilities with GRASP, and use inheritance only where it expresses a genuine, stable is-a relationship
Covers
Responsibility · Collaboration · Inheritance
On this page
  1. 1. Concept / Definition
  2. 2. Why It Exists
  3. 3. Core Concepts
  4. 4. How It Works Internally — Classes on the JVM
  5. 4A. Deep Dive — Designing Classes Responsibility-First
  6. 5. APIs / Features / Syntax
  7. 6. Practical Examples
  8. 7. Common Problems & Mistakes
  9. 8. Performance & Production Considerations
  10. 9. Architecture & Design Considerations
  11. 10. Interview Questions & Answers

1Concept / Definition

A class is the definition of a set of objects that share the same structure (state) and behaviour. In design terms a class is best understood as a bundle of responsibilities: things it knows and things it does, fulfilled alone or through collaboration with other classes. Inheritance lets a class be defined as a specialisation of another, sharing its interface and implementation.

Design viewQuestion
ResponsibilityWhat does this class know and do?
CollaborationWhich other classes does it need to fulfil its responsibilities?
InheritanceIs it a specialisation (is-a) of another class?
Table 1 — Three views of a class

2Why It Exists

Early OO modelling focused on nouns and data (entity-relationship thinking carried into classes). Responsibility-Driven Design (Wirfs-Brock & Wilkerson, 1989) and CRC cards (Beck & Cunningham, 1989) shifted focus to behaviour and collaboration. Craig Larman's GRASP patterns (Applying UML and Patterns, 1997) provided principles for assigning responsibilities. Meanwhile, experience with deep inheritance hierarchies in frameworks led the Gang of Four (1994) to recommend favouring composition — a theme developed in Topic 1.4.

3Core Concepts

3.1 Responsibility

A responsibility is an obligation of a class: either knowing (information it maintains or can compute) or doing (actions it performs or coordinates).

KindExamples for Order
KnowingIts lines, status, customer ID; its total (computed)
DoingAdding lines, placing itself, calculating total, validating max quantity
Coordinating(not Order) — CheckoutService coordinates Order, Payment and Stock
Table 2 — Responsibilities
CRC cards: class, responsibilities, collaborators
Figure 1 — CRC cards: class, responsibilities, collaborators
Assigning responsibilities (GRASP)
Figure 2 — Assigning responsibilities (GRASP)
JAVA
// Information Expert: Order has the lines, so Order computes the total
public Money total() {
    return lines.stream().map(OrderLine::subtotal).reduce(Money.ZERO, Money::plus);
}
 
// Creator: Order contains OrderLines, so Order creates them
public void addLine(Product product, int quantity) {
    lines.add(new OrderLine(product.sku(), quantity, product.price()));
}
 
// Pure Fabrication: persistence is not a domain responsibility → repository
public interface OrderRepository {
    void save(Order order);
    Optional<Order> byId(OrderId id);
}
Responsibility heuristicRationale
Keep related knowing and doing togetherHigh cohesion; Information Expert
Name classes after their responsibilityPriceCalculator, not PriceManager
A class's responsibilities should fit on an index cardForces focus (SRP, Chapter 2)
Separate domain responsibilities from technical onesSeparation of Concerns
Table 3 — Responsibility heuristics

3.2 Collaboration

Few responsibilities can be fulfilled by one object alone. Collaboration is objects sending messages to each other to fulfil a larger responsibility. Good collaborations are narrow (few collaborators, simple messages) and directed (dependencies point toward stable abstractions).

Collaboration: objects working together
Figure 3 — Collaboration: objects working together
JAVA
public final class CheckoutService {                 // coordinator
    private final ShippingPolicy shipping;
    private final PaymentGateway payments;
    private final OrderRepository orders;
 
    public CheckoutService(ShippingPolicy shipping, PaymentGateway payments,
                           OrderRepository orders) {
        this.shipping = shipping; this.payments = payments; this.orders = orders;
    }
 
    public Receipt checkout(Order order, PaymentDetails details) {
        Money amount = order.total().plus(shipping.costFor(order));
        Receipt receipt = payments.charge(amount, details);
        order.markPaid(receipt);
        orders.save(order);
        return receipt;
    }
}
Collaboration qualitySignal
Few collaboratorsConstructor with 2–4 dependencies, not 12
Messages at the right levelorder.markPaid(receipt) not order.setStatus(...)
Collaborators via abstractionsPaymentGateway interface, not a vendor SDK class
No chains through collaboratorsLaw of Demeter (Volume I, 5.5)
Table 4 — Healthy collaborations

A constructor with many dependencies is a strong signal that a class has too many responsibilities (SRP).

3.3 Inheritance

Inheritance defines a class as a subtype of another: it inherits the superclass's interface (so it can be used wherever the superclass is expected) and, for class inheritance, its implementation.

Inheritance: is-a hierarchies
Figure 4 — Inheritance: is-a hierarchies
KindJavaWhat is inherited
Interface inheritanceimplements, interface extendsContract only (plus default methods)
Implementation inheritanceextends a classContract, state and behaviour
Sealed hierarchysealed … permitsClosed set of known subtypes
Table 5 — Kinds of inheritance
JAVA
// Sealed hierarchy: closed, known variants — a good use of inheritance in modern Java
public sealed interface Payment permits CardPayment, BankTransfer { Money amount(); }
public record CardPayment(Money amount, CardToken token) implements Payment { }
public record BankTransfer(Money amount, Iban from) implements Payment { }
Use inheritance when…Avoid it when…
The subclass truly is a kind of the superclass everywhere (Liskov, Chapter 4)You only want to reuse some code
The superclass was designed and documented for extensionThe superclass is from another team/library and not designed for it
The hierarchy is stable and shallowVariation occurs in several independent dimensions
You want polymorphism over a closed set (sealed)Behaviour must change at runtime
Table 6 — Inheritance guidelines

4How It Works Internally — Classes on the JVM

1. The class loader loads a class file, verifies it and creates a Class object with metadata in Metaspace.

2. Each class has a virtual method table (vtable) for dispatch; interfaces use itable/dispatch stubs.

3. A call on a superclass or interface type uses dynamic dispatch to the runtime type's method; the JIT devirtualises monomorphic or bimorphic call sites and inlines them.

4. Constructors chain: a subclass constructor always calls a superclass constructor first — calling overridable methods from a constructor exposes partially initialised subclasses (a classic bug).

5. final classes/methods and sealed hierarchies give the JIT and compiler more certainty (exhaustive switches, easier devirtualisation).

JAVA
// Constructor calling an overridable method — dangerous
class Base {
    Base() { init(); }  // calls subclass override before subclass fields are set
    void init() { }
}
class Sub extends Base {
    private final List<String> items = new ArrayList<>();
    // NullPointerException: items not yet assigned
    @Override void init() { items.add("x"); }
}

4ADeep Dive — Designing Classes Responsibility-First

4A.1 From use case to classes

StepOutput
1. List system responsibilities from a use case"validate cart", "price order", "charge payment", "reserve stock"
2. Identify candidate classes from domain languageCart, Order, PricingPolicy, PaymentGateway, Inventory
3. Assign each responsibility (GRASP)Order computes total; PricingPolicy applies discounts
4. Walk through scenarios with CRC cardsDiscover missing collaborators and misplaced responsibilities
5. Refine: split overloaded classes, merge trivial onesCohesive classes with clear names
Table 7 — Responsibility-driven design process

4A.2 Class stereotypes (Wirfs-Brock)

StereotypeRoleJava/Spring example
Information holderKnows and provides informationRecords, entities
StructurerMaintains relationships between objectsAggregates, collections
Service providerPerforms workPriceCalculator
ControllerMakes decisions, directs othersApplication service
CoordinatorDelegates, little decision-makingFacade, orchestrator
InterfacerTransforms information between system partsControllers, adapters, mappers
Table 8 — Role stereotypes

A class with more than one stereotype (e.g. controller and interfacer and information holder) usually has too many responsibilities.

5APIs / Features / Syntax

FeatureDesign use
class, final classService providers, entities; final unless designed for extension
recordInformation holders (values, DTOs)
interfaceRoles and collaboration contracts
abstract classShared partial implementation designed for extension
sealed … permits, non-sealedControlled inheritance
@OverrideCompiler-checked overriding
Constructor injectionExplicit collaborators
Table 9 — Java class design features

6Practical Examples

6.1 CRC session outcome

ClassResponsibilitiesCollaborators
CartKnow items; add/remove; compute subtotalCartItem, Money
OrderKnow lines/status; place; mark paid; totalOrderLine, Money
CheckoutServiceCoordinate checkoutOrder, ShippingPolicy, PaymentGateway, OrderRepository
ShippingPolicyDecide shipping costOrder, RateTable
Table 10 — Outcome of a CRC session

6.2 Refactoring a class with too many collaborators

JAVA
// Before: 9 dependencies — a sign of several responsibilities
public OrderService(OrderRepository r, PaymentGateway p, Mailer m, PdfRenderer pdf,
                    TaxTable t, StockClient s, AuditLog a, Metrics mt,
                    FeatureFlags f) { … }
 
// After: responsibilities split; the service coordinates three collaborators
public CheckoutService(OrderRepository orders, PaymentGateway payments,
                       DomainEvents events) { … }
// Mail, PDF, audit and metrics react to OrderPaid events; tax lives in PricingPolicy

6.3 Replacing implementation inheritance with interface + composition

JAVA
// Before: ReportService extends BaseService to reuse logging and transactions
// After: implements its role interface and composes collaborators
public final class ReportService implements ReportGenerator {
    private final ReportRepository reports;
    private final ReportRenderer renderer;
    public ReportService(ReportRepository reports, ReportRenderer renderer) {
        this.reports = reports; this.renderer = renderer;
    }
    public byte[] generate(ReportId id) { return renderer.render(reports.byId(id)); }
}

7Common Problems & Mistakes

  • Data-first classes with getters/setters and logic elsewhere.
  • Manager/Util classes accumulating unrelated responsibilities.
  • Too many collaborators injected into one class.
  • Inheritance for code reuse (BaseService, AbstractController with helpers).
  • Deep hierarchies (more than two or three levels).
  • Overridable calls from constructors.
  • Non-final classes by default, inviting unintended extension.

8Performance & Production Considerations

AspectConsideration
Dynamic dispatchInlined for monomorphic/bimorphic sites; megamorphic sites (many implementations at one call site) are slower
Class countMore small classes → slightly more Metaspace and class-loading time; negligible for most services
finalHelps readers and tools more than the JIT (which speculates anyway)
StartupLarge class graphs affect startup; AOT caches help (Book 4)
Table 11 — Runtime aspects

9Architecture & Design Considerations

  • Responsibility assignment scales: modules and services are also bundles of responsibilities (Chapter 2).
  • Collaboration patterns within a service (coordinator + experts) mirror orchestration vs choreography between services.
  • Framework base classes (inheritance-based frameworks) are a coupling risk; prefer composition-friendly frameworks.
  • Code conventions: final by default, records for data, constructor injection for collaborators.

10Interview Questions & Answers

Q1BasicConcept

What is a class in object-oriented design?

A class defines a set of objects that share the same structure, their state, and the same behaviour, their methods. In design terms the more useful view is that a class is a bundle of responsibilities: things it knows and things it does, fulfilled alone or by collaborating with other classes. Rebecca Wirfs-Brock's responsibility-driven design starts from behaviour: list what the system must do, assign each responsibility to the class best placed to fulfil it, and let the fields follow. Order knows its lines and computes its total, while CheckoutService coordinates payment and stock. Starting from data instead tends to produce anemic classes with logic scattered across services.

Q2BasicConcept

What is a CRC card?

A CRC card is an index card with three parts: the Class name, its Responsibilities, and its Collaborators, the other classes it needs to fulfil them. Kent Beck and Ward Cunningham introduced CRC cards in 1989 as a lightweight way to design object models. A team writes one card per candidate class, then walks through a use case such as checkout, passing control from card to card. Missing collaborators, misplaced responsibilities and overloaded classes become obvious quickly. The small card is a deliberate constraint: if a class's responsibilities do not fit, it is probably doing too much. Cards are cheap to rewrite, which encourages exploring alternatives before any code exists.

Q3IntermediateConcept

What is the GRASP Information Expert principle?

Information Expert says: assign a responsibility to the class that already has the information needed to fulfil it. If Order holds its OrderLines, Order should compute its total, rather than an OrderService pulling the lines out through getters and adding them up. Keeping behaviour next to its data raises cohesion, avoids getters that expose internals, and means a change to how lines are stored stays inside one class. It is the most frequently applied of Craig Larman's GRASP principles. It is not absolute, though: if following it would drag persistence or UI concerns into a domain class, Pure Fabrication suggests a separate class, such as a repository, instead.

Q4IntermediateTrade-off

What is the difference between interface and implementation inheritance?

Interface inheritance shares only a contract. A class that implements an interface promises to provide certain methods and can be used polymorphically, but it inherits no state and, apart from default methods, no implementation. Implementation inheritance, extending a class, shares the contract plus fields, behaviour and the constructor chain. That makes the subclass depend on the superclass's internal details, so a change in the parent can silently break children, the fragile base class problem. Prefer interfaces plus composition for reuse and variation. Use implementation inheritance only for a genuine, stable is-a relationship where the superclass was designed and documented for extension, or for a closed sealed hierarchy.

Q5Senior EngineerDesign

What does a constructor with many dependencies indicate?

It usually means the class has several responsibilities. Each injected dependency is a collaborator, and when an OrderService needs nine of them, such as a repository, payment gateway, mailer, PDF renderer, tax table, stock client, audit log, metrics and feature flags, it is coordinating several unrelated concerns. Such classes change for many reasons and are hard to test. The fix is to split by responsibility: keep a coordinator with two to four collaborators, move rules such as tax into the domain object or policy that owns them, and let side effects like mail, PDF and audit react to a domain event such as OrderPaid. Hiding the dependencies behind a facade is not a fix.

Q6Senior EngineerTricky

Why is calling an overridable method from a constructor dangerous?

Because Java runs the superclass constructor before the subclass's field initialisers and constructor body. If Base's constructor calls an overridable method such as init(), dynamic dispatch runs the subclass's override while the subclass's fields still hold default values: null, zero or false. Even final fields are not yet assigned. In the classic example, Sub's init() adds to an items list that is still null and throws a NullPointerException, or worse, silently works on broken state. The rule is that constructors should call only private, static or final methods. If setup needs subclass input, pass it as a constructor argument or use a static factory that fully constructs the object before calling any hooks.

Q7Technical ArchitectTrade-off

When are sealed hierarchies a good use of inheritance?

Sealed hierarchies are a good use of inheritance when the set of subtypes is closed and known, and the variation is part of the domain rather than an extension point for others. A Payment that is either a CardPayment or a BankTransfer, or a Redemption result with three outcomes, are typical cases. Declaring the type sealed with permits lets the compiler check exhaustive switch pattern matching, so adding a new subtype breaks every switch that does not handle it, which is exactly what you want. Records as permitted subtypes keep each variant small and immutable. If third parties must add variants, or variants change often, use an open interface with strategies instead.

Q8Solution ArchitectDesign

How does responsibility-driven design relate to service design?

Responsibility-driven design scales from classes to services. A service, like a class, is a bundle of responsibilities, and the same questions apply: which capability does it own, what information does it need, and with whom must it collaborate? Information Expert becomes assigning a capability to the service that owns the data, so pricing lives with the catalogue data it needs rather than being assembled from remote calls. Narrow, directed collaborations become small APIs and dependencies that point towards stable services. A coordinator class mirrors an orchestrating service, while event-driven collaboration mirrors choreography. Clear ownership of each responsibility is what keeps services from becoming a chatty, tightly coupled distributed monolith.

Newsletter

Get new tutorials every week

New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.

Almost done! Check your inbox and click the confirmation link to complete your subscription.
Keep going in Book 5

Software Design & Design Patterns

Volume I · Topic 1.1

Software Design

Software Design Foundations & Principles

Volume I · Topic 1.2

Why Software Design Matters

Software Design Foundations & Principles

Volume I · Topic 1.3

Good vs Poor Design

Software Design Foundations & Principles

Volume II · Topic 1.1

Objects

Object-Oriented Design & SOLID

Volume II · Topic 1.3

Relationships

Object-Oriented Design & SOLID

Volume III · Topic 1.1

What Is a Design Pattern?

Creational & Structural Design Patterns

Volume III · Topic 1.2

Pattern Categories

Creational & Structural Design Patterns

Volume III · Topic 1.3

Pattern vs Framework

Creational & Structural Design Patterns

Volume IV · Topic 1.1

Strategy Pattern

Behavioral Design Patterns

Volume IV · Topic 1.2

Strategy in Java

Behavioral Design Patterns

Volume IV · Topic 1.3

Strategy vs Conditional Logic

Behavioral Design Patterns

Volume V · Topic 1.1

Layered Architecture

Enterprise & Application Design Patterns

Volume V · Topic 1.2

Service Layer

Enterprise & Application Design Patterns

Volume V · Topic 1.3

Repository

Enterprise & Application Design Patterns

Volume VI · Topic 1.1

What Is Refactoring?

Refactoring, Code Quality & Evolutionary Design

Volume VI · Topic 1.2

Why Refactor?

Refactoring, Code Quality & Evolutionary Design

Volume VI · Topic 1.3

Refactoring Safety

Refactoring, Code Quality & Evolutionary Design

Volume VII · Topic 1.1

Method-Level Design

Advanced Design & Architecture-Level Design Decisions

Volume VII · Topic 1.2

Class-Level Design

Advanced Design & Architecture-Level Design Decisions

Volume VII · Topic 1.3

Module-Level Design

Advanced Design & Architecture-Level Design Decisions