Class Responsibility Realignment

v1.0.0

Redistribute methods and fields to the classes that own them, repair broken inheritance hierarchies, and extend unmodifiable library classes. Use when: a met...

0· 130· 1 versions· 0 current· 0 all-time· Updated 11h ago· MIT-0
byHung Quoc To@quochungto

Install

openclaw skills install bookforge-class-responsibility-realignment

Class Responsibility Realignment

When to Use

You have identified (via code-smell-diagnosis or direct inspection) that behavior or data is in the wrong class. The concrete signals are:

  • A method calls six getter methods on another object to do its work — it belongs in that other class (Feature Envy)
  • One logical change requires touching five files — the behavior is scattered and needs a home (Shotgun Surgery)
  • A class does nothing but forward calls to another class — it is a hollow Middle Man
  • Two classes share internal knowledge they should not — they are Inappropriately Intimate
  • A cluster of fields migrates together across method signatures — it is a Data Clump waiting to be extracted
  • A hierarchy has duplicate methods across siblings, or specialized behavior sitting in the wrong layer
  • A library class is missing one or two methods you need repeatedly but cannot add directly

The core insight from Fowler: One of the most fundamental decisions in object design is deciding where to put responsibilities. Getting it wrong the first time is not a problem — refactoring exists to correct it. The moment you realize a method uses another class's data more than its own, you have the diagnosis and the prescription simultaneously: Move Method.


Context and Input Gathering

Required Input

  • The target class or classes. Either a file path, a class name, or the output of a code-smell-diagnosis report. Why: responsibility decisions are made relative to the data each class owns — you cannot realign without knowing what each class contains.
  • The smell classification. Which of the catalog smells applies (Feature Envy, Shotgun Surgery, Middle Man, Inappropriate Intimacy, Data Clumps, hierarchy smell, library gap). Why: each smell maps to a distinct set of refactorings; applying the wrong one produces a different structural problem.

Observable Context

Before touching anything:

Scan targets:
  - Which class does this method call getters on most? → Move Method candidate
  - Which fields travel together in parameter lists? → Extract Class / Data Clumps
  - How many files change together for one logical edit? → Shotgun Surgery scope
  - How many methods on class A just call class B? → Middle Man threshold
  - Which subclass methods are identical? → Pull Up Method candidates
  - Does a subclass throw NotImplemented or ignore parent fields? → Refused Bequest

Default Assumptions

  • If the smell is ambiguous between Feature Envy and Inappropriate Intimacy: start with Move Method; see which class becomes more cohesive
  • If Shotgun Surgery scope is large (>5 files): consolidate into a new class rather than an existing one — no existing class is the right home if the behavior is truly scattered
  • If Move Field is needed alongside Move Method: move the field first (Fowler's preference — it stabilizes the data layout before moving the behavior)

Process

Step 1: Classify the Structural Problem

ACTION: Map the symptoms to one of the three problem clusters below.

WHY: Each cluster uses a different set of refactorings. Misclassification wastes a move or creates a new smell.

Cluster A — Misplaced behavior (flat class graph)

SymptomRefactoring
Method uses another class's data more than its ownMove Method
Field is referenced more by another classMove Field
Class does too much; subset of fields/methods coheres separatelyExtract Class
Class does too little; its responsibilities fit an absorbing classInline Class
Client navigates a.getB().getC() chainsHide Delegate (add delegating method on server)
Server delegates more than half its interface, adds no valueRemove Middle Man
Library class missing 1–2 methodsIntroduce Foreign Method
Library class missing 3+ methodsIntroduce Local Extension

Cluster B — Hierarchy misalignment

SymptomRefactoring
Identical method/field in two or more subclassesPull Up Method / Pull Up Field
Constructors in subclasses with mostly identical bodiesPull Up Constructor Body
Superclass method only meaningful in one subclassPush Down Method
Superclass field only used in one subclassPush Down Field
Subset of features used only in some instancesExtract Subclass
Two unrelated classes share common behaviorExtract Superclass
Several clients use only a subset of one class's interfaceExtract Interface
Subclass and superclass have converged — barely differentCollapse Hierarchy
Two subclass methods do the same steps in the same order, but differentlyForm Template Method
Subclass uses only part of superclass interface, or inherits wrong dataReplace Inheritance with Delegation
Delegation covers the full interface, delegation methods are boilerplateReplace Delegation with Inheritance

Cluster C — Encapsulation balance

SymptomAction
Clients know too much about delegate's internalsAdd Hide Delegate methods on server
Server has grown into a pass-through layerRemove Middle Man — let clients talk to delegate

Step 2: Apply Cluster A — Misplaced Behavior

ACTION: Execute the mechanics for the identified refactoring(s).

WHY: Each step produces a compilable intermediate state; verifying after each move catches errors before they compound.

Move Method

When a method is more interested in another class than its own:

  1. Examine all features the method uses. If several features from the same source class are involved, consider moving them as a group — moving a clutch of methods is sometimes easier than moving them one at a time.
  2. Check subclasses and superclasses of the source class for other declarations. If other declarations exist, the move may be blocked unless the target class can also express the polymorphism.
  3. Declare the method in the target class. A rename that makes better sense in the target context is appropriate.
  4. Copy the method body. Adjust it to work in its new home — four options for referencing the source object: (a) move the feature to the target class as well, (b) create or use an existing reference from target to source, (c) pass the source object as a parameter, (d) pass the specific value as a parameter.
  5. Turn the source method into a simple delegation (return target.methodName()). Compile and test.
  6. Decide whether to remove the delegation. Leaving it is easier if there are many callers; removing it is cleaner if callers can be updated.

Move Field

When a field is used more by another class:

  1. If the field is public, use Encapsulate Field first. If many methods access it, use Self Encapsulate Field so only accessors touch the raw field.
  2. Create the field with getter/setter in the target class. Compile.
  3. Determine how to reference the target object from the source. Use an existing field or method; if none exists, create one (possibly temporary).
  4. Remove the field from the source class. Replace all references with calls to the target's accessor.
  5. Compile and test.

Extract Class

When a class is doing work that should be done by two:

  1. Decide the split. A useful test: remove a field — do the remaining fields and methods still make sense without it? If yes, you have found an extraction boundary.
  2. Create the new class. Rename the old class if its name no longer matches its reduced scope.
  3. Move fields first (Move Field), then move methods (Move Method) — start with lower-level methods (called, not calling) and work toward higher-level ones.
  4. Review and reduce interfaces after each move. If a two-way link exists, examine whether it can become one-way.
  5. Decide visibility of the new class. If exposing it, decide between reference object (mutable, aliasing possible) and immutable value object.

Inline Class

When a class is no longer pulling its weight:

  1. Declare the public protocol of the source class on the absorbing class. Delegate all methods to the source class temporarily.
  2. Change all external references from the source class to the absorbing class.
  3. Use Move Method and Move Field to move features from the source to the absorbing class until nothing is left.
  4. Delete the empty class.

Hide Delegate vs. Remove Middle Man — the encapsulation balance

Fowler's observation: these two are inverse refactorings and neither is permanently correct. "Refactoring means you never have to say you're sorry — you just fix it." Apply the rule by tracking:

  • Hide Delegate: Client calls manager = john.getDepartment().getManager(). Add getManager() directly to Person so the client does not need to know about Department. Do this for each method on the delegate that clients use. Remove the delegate accessor if clients no longer need it.
  • Remove Middle Man: After applying Hide Delegate repeatedly, Person has grown a long list of one-liner delegation methods — it is now a Middle Man. Add an accessor for the delegate. For each client use of a delegation method, redirect the client to call the delegate directly. Remove each delegation method as its clients migrate.

The judgment call: Hide more when the delegate's implementation is likely to change (clients should not be coupled to it). Remove the middle man when the delegating layer adds no value and the cost of maintaining all those one-liners exceeds the coupling cost.

Library Extension Strategy

When you cannot modify a class but need additional behavior from it:

1–2 methods needed → Introduce Foreign Method

  1. Create a method in the client class that does what is needed.
  2. Make an instance of the server class the first parameter.
  3. Comment the method: // foreign method; should be on [ServerClass]. Why: this marks it for migration if you later gain access to the source, and enables grep-based discovery of all foreign methods for a given class.
  4. The method must not access any features of the client class — it should feel like it belongs on the server.

3+ methods needed → Introduce Local Extension

  1. Decide between subclass and wrapper:
    • Subclass (preferred): simpler, less code. Constraint: apply at object-creation time. If the original is immutable, a copy constructor suffices and aliasing is not a problem.
    • Wrapper: required when (a) you cannot control creation of the original objects, (b) the original is mutable and you need the extension and the original to share state, or (c) you need to apply the extension to existing objects. Tradeoff: must delegate all original class methods, and symmetrical equality checks (a.equals(b) where a is original and b is wrapper) cannot be fully hidden.
  2. Add converting constructors — one that takes an instance of the original as argument.
  3. Add new methods to the extension class. Move any existing foreign methods defined for this class onto the extension.
  4. Replace uses of the original class with uses of the extension where the new methods are needed.

Step 3: Apply Cluster B — Hierarchy Repair

ACTION: Select and apply the hierarchy refactoring identified in Step 1.

WHY: Hierarchy smells compound — duplicate methods in subclasses create two maintenance points; misplaced fields make Pull Up Method harder. Address the fields before the methods.

Pull Up Field / Pull Up Method

Fields first, then methods. Why: Pull Up Method may depend on the field being in the superclass already.

  • Pull Up Field: Inspect that all uses of the candidate fields are equivalent across subclasses. Rename if needed. Create the field in the superclass; delete from subclasses. Mark protected if subclasses need direct access.
  • Pull Up Method: Inspect methods to confirm they are identical (or can be made identical via algorithm substitution). If similar but not identical, see Form Template Method. If the method references a feature that is only on the subclass, generalize that feature first (Pull Up the feature, or declare an abstract method on the superclass). Copy one body to the superclass; delete from subclasses one at a time, compiling and testing after each deletion.
  • Pull Up Constructor Body: Constructors cannot be inherited. Extract the common initialization code into a superclass constructor; call it via super(...) from each subclass. If common code must run after subclass-specific initialization, use Extract Method on the common post-init logic and Pull Up Method to put it in the superclass, then call it explicitly from the subclass constructor.

Push Down Method / Push Down Field

When superclass behavior is only relevant to some subclasses:

  • Declare the method/field in all subclasses that need it. Copy the body. Remove from the superclass. Remove from subclasses that do not need it. Compile and test.
  • If the superclass is abstract and the method should still be accessible via a superclass variable, declare it abstract on the superclass instead of removing it.

Extract Subclass

When a class has features used only in some instances:

  1. The main alternative is Extract Class (delegation vs. inheritance). Choose Extract Subclass when: the variation is fixed at construction time (the object's kind does not change after creation), the variation is single-dimensional (one axis of difference), and the class being extended is modifiable. Why: subclassing is simpler — the class-based behavior is changed by plugging in different components with Extract Class, but Extract Subclass requires only Push Down Method and Push Down Field.
  2. Choose Extract Class instead when: the object's kind varies at runtime, or the class must vary in multiple dimensions simultaneously. Fowler: "If you want the class to vary in several different ways, you have to use delegation for all but one of them."
  3. Define the new subclass. Provide constructors. Find all calls to the superclass constructor that should use the subclass — replace with subclass constructor calls.
  4. Use Push Down Method and Push Down Field to move specialized features. Unlike Extract Class, work on methods first, data last.
  5. Find boolean or type-code fields that now encode information already expressed by the hierarchy — eliminate them by replacing their getter with a polymorphic constant method.

Extract Superclass

When two classes share common behavior:

  1. Create a blank abstract superclass. Make both classes its subclasses.
  2. Move common fields first (Pull Up Field). Move common methods (Pull Up Method). If method signatures differ but purpose is the same, rename first.
  3. If methods have different bodies doing the same thing, try Substitute Algorithm to unify them.
  4. If methods have the same signature but different bodies that cannot be unified, declare the method abstract on the superclass.
  5. Check clients — if they use only the common interface, change their type declarations to the superclass.

Extract Interface

When several clients use only a subset of a class's interface, or when a class needs to work with any class that can handle certain requests:

  • Create an empty interface. Declare the common operations. Declare the relevant classes as implementing the interface. Update client type declarations to use the interface.
  • Unlike Extract Superclass, Extract Interface cannot move common code — only the contract. If common code is needed too, combine with Extract Class (for the shared implementation).

Collapse Hierarchy

When a subclass and superclass have converged:

  • Choose which class to remove (usually the subclass). Pull Up or Push Down all methods and fields to the surviving class. Update all references. Remove the empty class.

Form Template Method

When two subclass methods perform the same steps in the same order but the steps are implemented differently:

  1. Decompose each method using Extract Method so that each extracted piece is either identical across subclasses or completely different — no partial overlap.
  2. Pull Up the identical extracted methods to the superclass.
  3. Rename the different methods so they have the same signature in both subclasses. Why: making the signatures identical makes the calling method identical across subclasses — it calls the same method names in the same order.
  4. Pull Up the calling method on one of the subclasses. Declare the different methods as abstract on the superclass.
  5. Remove the calling method from the other subclass.

Result: the superclass holds the invariant algorithm; subclasses supply only the steps that vary. This is the Template Method pattern — adding a new variant requires only a new subclass that overrides the abstract steps.

Replace Inheritance with Delegation

When a subclass uses only part of the superclass interface or inherits data that does not make sense for it (Refused Bequest, strong form):

  1. Create a field in the subclass that refers to the superclass — initialize it to this so delegation and inheritance can coexist during the transition.
  2. Change each subclass method to use the delegate field instead of implicit inheritance. Compile and test after each change. Note: methods that call super cannot be changed until the inheritance link is broken — they would recurse.
  3. Remove the inheritance declaration. Change the delegate field from this to a new instance of the former superclass.
  4. For each superclass method used by clients, add a simple delegating method on the subclass. Compile and test.

Contraindications: do not apply when the subclass uses all methods of the superclass (delegation is boilerplate without benefit), or when the delegate is shared and mutable (data sharing cannot be replicated with delegation).

Replace Delegation with Inheritance

When a class delegates to another class for the full interface and the delegating methods are pure boilerplate:

  1. Precondition: the delegating class uses all of the delegate's methods. If not, use Remove Middle Man or Extract Interface instead.
  2. Precondition: the delegate is not shared and mutable. If it is, delegation must remain (you cannot replicate shared mutable state via inheritance).
  3. Make the delegating class a subclass of the delegate.
  4. Set the delegate field to the object itself. Remove simple delegation methods. Compile and test.
  5. Replace remaining uses of the delegate field with direct calls. Remove the delegate field.

Step 4: Verify the Realignment

ACTION: Confirm the structural goals are achieved.

WHY: It is possible to complete all mechanics correctly but still have the smell — if the wrong refactoring was chosen for the symptom, or if a second smell was hidden beneath the first.

Verify each:

  • Each class has a name that describes a single responsibility. If the name requires "and" to describe what it does, the split may be incomplete.
  • No method calls another class's getters more than its own class's fields. If Feature Envy persists, a deeper extraction is needed.
  • No class delegates more than half its public methods without adding any logic. If Middle Man persists, Remove Middle Man was not fully applied.
  • Hierarchy: identical code does not appear in two sibling subclasses. Pull Up was not applied completely if it does.
  • The delegation/inheritance decision is correct: delegation if the class varies in multiple dimensions or at runtime; inheritance if the variation is single-axis and fixed at construction.

Key Principles

1. Move Field before Move Method when doing both. A method moved before its data is moved will still reference the old class via getter calls. Moving the field first stabilizes the data layout; the method move then resolves cleanly.

2. The encapsulation balance is not fixed. Hide Delegate protects clients from implementation changes. Remove Middle Man removes hollow layers. Apply whichever is appropriate given current coupling pressure — and reapply the inverse when conditions change.

3. Inheritance vs. delegation: the variation-dimensions rule. Fowler's criterion: if you need the class to vary in only one way, subclassing (Extract Subclass) is simpler. If you need it to vary in several different ways, you must use delegation for all but one of them — a class can only have one superclass, but it can hold multiple delegate objects.

4. Foreign methods are a workaround, not a destination. Comment them as // foreign method; should be on [ServerClass] so they can be found and migrated if you gain access to the server class. If the count grows beyond 2, promote to a local extension.

5. Form Template Method is Extract Method + Pull Up. It is a composed refactoring, not a single step. The key is the decomposition: extracted methods must be either identical (candidates for Pull Up) or completely different (candidates for abstract methods). Partial overlap defeats the pattern.


Examples

Example 1: Feature Envy → Move Method

Scenario: Account.overdraftCharge() calls _type.isPremium() and uses _type.daysOverdrawn() — most of its logic is about AccountType, not Account.

Diagnosis: Feature Envy. The method is more interested in AccountType than in Account.

Apply Move Method:

  1. Copy overdraftCharge to AccountType. Pass daysOverdrawn as a parameter (option d — pass the specific value).
  2. Replace Account.overdraftCharge() body with return _type.overdraftCharge(_daysOverdrawn);
  3. Compile and test. Callers of account.overdraftCharge() still work — the source is now a delegating method.
  4. Decision: several account types will be added, each needing its own overdraft rule. Remove the delegation in Account and redirect all callers to _type.overdraftCharge(_daysOverdrawn) directly.

Result: AccountType owns overdraft logic. New account types require changes only in AccountType — Shotgun Surgery is eliminated.


Example 2: Data Clumps → Extract Class

Scenario: Person has _officeAreaCode and _officeNumber as separate fields. Both appear in method signatures throughout the codebase. Deleting _officeAreaCode makes _officeNumber meaningless without context.

Diagnosis: Data Clumps. These two fields form a natural object.

Apply Extract Class:

  1. Create TelephoneNumber class.
  2. Add a link from Person to TelephoneNumber: private TelephoneNumber _officeTelephone = new TelephoneNumber().
  3. Move Field: move _officeAreaCode to TelephoneNumber. Update Person's accessors to delegate: getOfficeAreaCode()_officeTelephone.getAreaCode().
  4. Move Field: move _officeNumber. Move Method: move getTelephoneNumber() to TelephoneNumber.
  5. Decide visibility: expose TelephoneNumber as a reference object (aliasing allowed) or make it immutable (safer).

Result: TelephoneNumber is a named concept. It can be reused by HomeAddress, MobileContact, and others without duplicating the area-code/number pair.


Example 3: Extract Subclass vs. Extract Class — the variation-dimensions decision

Scenario: JobItem has an _isLabor boolean that controls behavior in getUnitPrice(). Labor items use employee.getRate(); parts items use _unitPrice. The _employee field is null for parts items.

Classification step:

  • Is the variation fixed at construction time? Yes — a job item is either a labor item or a parts item from the start.
  • Is the variation single-dimensional? Yes — there is one axis (labor vs. parts).
  • Conclusion: Extract Subclass.

Apply Extract Subclass (creating LaborItem):

  1. Create LaborItem extends JobItem.
  2. Push Down Method: getEmployee() moves to LaborItem.
  3. Push Down Field: _employee moves to LaborItem.
  4. Self Encapsulate Field _isLabor; replace its getter with a polymorphic constant: JobItem.isLabor() returns false, LaborItem.isLabor() returns true.
  5. Apply Replace Conditional with Polymorphism on getUnitPrice(): JobItem returns _unitPrice; LaborItem returns _employee.getRate().
  6. Remove _isLabor field.

Contrast with delegation case: If job items needed to vary along a second axis (e.g., taxable vs. non-taxable), subclassing could cover only one axis. The second axis would require a delegate: JobItem holds a TaxStrategy delegate and a PricingStrategy delegate, with the hierarchy handling neither.


Example 4: Form Template Method

Scenario: TextStatement.value() and HtmlStatement.value() perform the same three steps (header, body loop, footer) but format them differently. The two methods are similar but not identical.

Apply Form Template Method:

  1. Move both methods to subclasses of a new Statement superclass.
  2. Extract Method on each differing step: headerString(), eachRentalString(), footerString() — extracted with identical signatures in both subclasses, different bodies.
  3. The calling method value() becomes identical in both subclasses: it calls headerString(), loops calling eachRentalString(), and calls footerString().
  4. Pull Up Method on value() to Statement. Declare headerString(), eachRentalString(), footerString() as abstract on Statement.
  5. Remove value() from both subclasses.

Result: Adding a PdfStatement requires only a new subclass that overrides three methods — the algorithm structure is untouchable.


References

FileContentsWhen to read
references/smell-catalog.mdFull detection criteria for all 22 smellsConfirming the smell classification in Step 1
references/refactoring-prescriptions.mdFull prescription trees per smellSelecting the correct conditional branch for each refactoring

Hub skill relationships:

  • code-smell-diagnosis — diagnose before realigning; this skill executes what that skill prescribes
  • data-organization-refactoring — when Data Clumps or Primitive Obsession are the primary smell (Extract Class for data, not behavior)
  • type-code-refactoring-selector — when the hierarchy problem involves a type code (Replace Type Code with Subclasses / State / Strategy)
  • conditional-simplification-strategy — when Replace Conditional with Polymorphism is needed after Extract Subclass
  • big-refactoring-planner — when multiple smells indicate a systemic design problem requiring coordinated multi-step refactoring

License

This skill is licensed under CC-BY-SA-4.0. Source: BookForge — Refactoring: Improving the Design of Existing Code by Martin Fowler and Kent Beck.

Related BookForge Skills

Install related skills from ClawhHub:

  • clawhub install bookforge-code-smell-diagnosis
  • clawhub install bookforge-data-organization-refactoring
  • clawhub install bookforge-type-code-refactoring-selector
  • clawhub install bookforge-conditional-simplification-strategy
  • clawhub install bookforge-big-refactoring-planner

Or install the full book set from GitHub: bookforge-skills

Version tags

bookforgevk97a17yp69265nycebm7adss3s854xvjcode-qualityvk97a17yp69265nycebm7adss3s854xvjlatestvk97a17yp69265nycebm7adss3s854xvjobject-oriented-designvk97a17yp69265nycebm7adss3s854xvjrefactoringvk97a17yp69265nycebm7adss3s854xvjresponsibilitiesvk97a17yp69265nycebm7adss3s854xvj

Runtime requirements

📚 Clawdis