# Extract Superclass

> Create a shared superclass for two classes with overlapping fields and methods, and pull the common parts up into it.

- kind: technique
- category: dealing-with-generalization
- canonical: https://aipatterns.dev/refactoring/techniques/extract-superclass
- inspired-by: https://refactoring.guru/extract-superclass

## Problem

Two unrelated classes have the same fields and some of the same methods. They were built separately, so the shared logic is duplicated and may already have drifted apart.

## Solution

**Create a new superclass, make both classes extend it, and move the shared fields and methods up.**

## Before

```ts
class WebhookEndpoint {
  constructor(readonly url: string, readonly secret: string) {}
  private failures = 0;
  recordFailure(): void { this.failures++; }
  isDisabled(): boolean { return this.failures >= 5; }
  deliver(event: DomainEvent) { return http.post(this.url, sign(event, this.secret)); }
}

class SlackIntegration {
  constructor(readonly webhookUrl: string, readonly channel: string) {}
  private errorCount = 0;
  recordFailure(): void { this.errorCount++; }
  isDisabled(): boolean { return this.errorCount >= 5; }
  deliver(event: DomainEvent) { return http.post(this.webhookUrl, { channel: this.channel, text: summarize(event) }); }
}
```

## After

```ts
abstract class Subscriber {
  private failures = 0;
  recordFailure(): void { this.failures++; }
  isDisabled(): boolean { return this.failures >= 5; }
  abstract deliver(event: DomainEvent): Promise<unknown>;
}

class WebhookEndpoint extends Subscriber {
  constructor(readonly url: string, readonly secret: string) { super(); }
  deliver(event: DomainEvent) { return http.post(this.url, sign(event, this.secret)); }
}

class SlackIntegration extends Subscriber {
  constructor(readonly webhookUrl: string, readonly channel: string) { super(); }
  deliver(event: DomainEvent) { return http.post(this.webhookUrl, { channel: this.channel, text: summarize(event) }); }
}
```

## Why

- Failure tracking exists once, so the disable threshold cannot differ by accident.
- Code that dispatches events can work with `Subscriber` and treat both kinds the same.
- The shared concept gets a name.

## Steps

1. Create an empty abstract superclass and make both classes extend it. Run the tests.
2. Unify names of matching fields and methods in both classes (`errorCount` becomes `failures`). Run the tests.
3. Move common fields up with [Pull Up Field](https://aipatterns.dev/refactoring/techniques/pull-up-field.md). Run the tests.
4. Move identical methods up with [Pull Up Method](https://aipatterns.dev/refactoring/techniques/pull-up-method.md). Declare methods with the same signature but different bodies as `abstract` in the superclass. Run the tests.
5. Change clients that can now depend on the superclass instead of either concrete class.

## Watch out

- Do not extract a superclass when either class already extends something else in a single-inheritance language. Use [Extract Class](https://aipatterns.dev/refactoring/techniques/extract-class.md) and composition, or [Extract Interface](https://aipatterns.dev/refactoring/techniques/extract-interface.md) for the shared contract.
- Do not create a superclass for classes that share code by coincidence but not a concept. A shared helper function is lighter and couples less.
- Shared state in a base class couples subclasses tightly. If the shared part is a self-contained concern (like failure tracking), a composed `FailureCounter` object may age better than inheritance.
- The inverse is [Collapse Hierarchy](https://aipatterns.dev/refactoring/techniques/collapse-hierarchy.md).

## Related

- Inverse: [Collapse Hierarchy](https://aipatterns.dev/refactoring/techniques/collapse-hierarchy.md).
- Fixes [Duplicate Code](https://aipatterns.dev/refactoring/smells/duplicate-code.md) and [Alternative Classes with Different Interfaces](https://aipatterns.dev/refactoring/smells/alternative-classes-with-different-interfaces.md).
- Alternatives: [Extract Class](https://aipatterns.dev/refactoring/techniques/extract-class.md) (composition), [Extract Interface](https://aipatterns.dev/refactoring/techniques/extract-interface.md) (contract only).

## Agent checklist

- [ ] Both classes represent the same concept, not only similar code.
- [ ] Neither class already needs a different base class.
- [ ] Names were unified before members were pulled up.
- [ ] Composition was considered for self-contained shared concerns.
