Agent Wranglers

SOLID and DRY Recommended

Prefer composition over inheritance

Reuse behaviour by holding an object that does the job, not by extending a class. Inherit only when the child truly is a kind of the parent.

How to do it

Put shared behaviour in a small object or function and pass it in through the constructor. Keep inheritance at most two levels deep. Mark classes final or sealed unless they are built to be extended. A framework base class you must extend, such as a model or controller, counts as level zero. Every subclass keeps the parent's contract.

Example

// Bad: a report extends a mailer only to reuse send()
class SalesReport extends Mailer { /* ... */ }

// Good: the report is given a mailer and uses it
class SalesReport {
  constructor(private readonly mailer: Mailer) {}
  send(to: EmailAddress) { this.mailer.send(to, this.render()); }
}
// Good: final by default, and the mailer is passed in
final class SalesReport
{
    public function __construct(private readonly Mailer $mailer) {}
    public function send(EmailAddress $to): void { $this->mailer->send($to, $this->render()); }
}

How to check

A linter fails on deep inheritance. Examples are Pylint too-many-ancestors and PHPMD DepthOfInheritance, set to fail above two levels. Pylint ignored-parents skips the framework's own classes. PHP-CS-Fixer final_class makes classes final by default, as Kotlin already does.

For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers architecture playbook: https://agentwranglers.se/playbook/architecture/solid-and-dry/composition-over-inheritance.