Composition vs inheritance
Essence
Inheritance is an is-a relationship ("a button is a view"): the subclass gets all of the parent's behaviour. Composition is a has-a relationship ("a screen has a logger"): the object is assembled from parts and delegates work to them. The rule of thumb is "prefer composition over inheritance"; subclass when you really need to extend a framework (UIViewController) or the domain model is genuinely hierarchical.
Example
// Inheritance: "is-a". A deep hierarchy is rigid.
class BaseViewController {}
class LoggingViewController: BaseViewController {}
// Composition: "has-a". Behaviour is injected and replaceable.
protocol Logger { func log(_ message: String) }
struct ConsoleLogger: Logger { func log(_ message: String) { print(message) } }
struct SilentLogger: Logger { func log(_ message: String) {} }
final class ProfileViewModel {
private let logger: any Logger
init(logger: any Logger = ConsoleLogger()) { self.logger = logger }
func load() { logger.log("loading profile") }
}
ProfileViewModel().load() // prints
ProfileViewModel(logger: SilentLogger()).load() // prints nothing
To add logging through inheritance you need a LoggingViewController, then a LoggingAnalyticsViewController... a combinatorial explosion. Composition lets you assemble any set of behaviours and swap them in tests.
Why composition is usually better
- Less coupling: the dependency is a protocol, not a concrete parent.
- Behaviour can change at runtime (inject a different
Logger). - No fragile base class and no single inheritance slot to spend.
- Works with
struct/enum/actor, which cannot inherit at all.
When inheritance is appropriate
- Subclassing a framework:
UIView,UIViewController,NSOperation. - You need subtype polymorphism with shared state; a protocol with an
extensionprovides behaviour only, not stored properties.