한국어 | English | 日本語
Senior Web Application Developer (8.8+ years)
Tech & Dev
engineering
Focusing on web frontend and backend development

0. The First and Second Principles of Object-Oriented Programming

Composition vs Inheritance
During my university days, object-oriented programming felt limited to polymorphism and inheritance. Developing in the industry, I profoundly realized that OOP is not merely an academic concept but a practical discipline. One might wonder why the first and second principles of OOP are discussed before delving into design patterns. Design patterns are 'the culmination of deliberation on what constitutes better code within the object-oriented paradigm.' To truly grasp the significance of various design patterns, it is essential to first understand the fundamental principles of object-oriented programming.
Inheritance, often the first concept encountered when learning OOP in school, is merely introduced early in the academic sequence. In actual industry practice, it is often an anti-pattern that should be avoided. This article will explain how and why to move away from inheritance through the lens of the first and second principles.

Is Inheritance Synonymous with Object-Oriented Programming?

Let’s imagine a developer writing their first enterprise object-oriented program. Following what they learned in school, where object-oriented programming is often equated with inheritance, they might boldly create a parent class and then utilize child classes that inherit from it. The code would likely resemble the example from the Head-First book below.

Inheritance as First Taught

class Duck
	{ swim(), display(), fly(), quack() }
class RedHeadDuck extends Duck
	{ swim(), display(), fly(), quack() }

class RubberDuck extends Duck
	{ swim(), display(), fly(){ null }, quack() }

When using inheritance, all functions present in the parent Duck class are acquired by its child Duck classes. The problem is that much like inheriting both assets and debts, a child Duck class is forced to extend with all parent functions, even those it doesn’t want, regardless of its specific needs. This introduces unnecessary constraints in development.

To resolve this, one can define ‘functions as the smallest separable units’ as interfaces, and then select and extend only the interfaces containing the necessary functions for the class being developed.

Interfaces Instead of Inheritance

interface Flyable { fly() }
interface Quackable { quack() }
class RedHeadDuck implements Flyable, Quackable {
	fly() { ... }
	quack() { ... }
}

class RubberDuck implements Quackable {
	quack() { ... }
}

This way, behaviors that once belonged to a parent class can be broken down into interfaces, allowing only the desired behaviors to be attached to an implementation class. However, when selecting and extending ‘behavioral unit’ interfaces, there’s the problem of having to implement them every time. Aren’t developers a ‘tribe of inconvenience’? Since implementing them every time is tedious, they reach a point where they pre-implement all ‘behavioral unit’ interfaces as ‘behavioral unit’ classes, and then selectively use these ‘behaviors’ as needed.

Composition, Not Just Interface ‘Implementation’

interface Flyable { fly() }
interface Quackable { quack() }
class NotFlyable implements Flyable { fly() { ... } }
class SuperFlyable implements Flyable { fly() { ... } }
class ShoutQuackable implements Quackable { quack() { ... } }
class QuiteQuackable implements Quackable { quack() { ... } }
class RedHeadDuck {
	interface Flyable = new SuperFlyable();
	interface Quackable = new ShoutQuackable();
	doFly() { Flyable.fly() }
	doQuack(){ Quackable.quack() }
}

class RubberDuck {
	interface Flyable = new NotFlyable();
	interface Quackable = new QuiteQuackable();
	doFly(){ Flyable.fly() }
	doQuack(){ Quackable.quack() }
}

Instead of implementing interfaces every time, we selectively compose pre-implemented concrete classes of interfaces. This allows us to freely attach, detach, and swap desired behavioral interface implementations. Thus, interfaces evolve beyond the university-taught concept of merely being a class template. It’s better to think of them as a ‘variable’ capable of holding classes that implement specific behaviors or characteristics. This, fundamentally, is why we learn about polymorphism.

The First and Second Principles of Object-Oriented Programming

The concepts discussed above can ultimately be summarized into the following two principles.

Prefer ‘Interface - Composition’ over ‘Class - Inheritance’

With ‘inheritance’, a class inherits everything its parent class possesses, regardless of need. Like LEGO blocks, ‘composition’, which allows selective inclusion of desired implementations, offers greater extensibility.

Compose with ‘Interfaces’ rather than ‘Concrete Classes’

Implementations can change at any time. When structuring logic within a class, compose it flexibly through interfaces, not concrete classes.

class RedHeadDuck {
	class SuperFlyable; 
	class ShoutQuackable; 
	doFly() { SuperFlyable.fly() }
	doQuack(){ ShoutQuackable.quack() }
}
class RedHeadDuck {
	interface Flyable = new SuperFlyable(); 
	interface Quackable = new ShoutQuackable(); 
	doFly() { Flyable.fly() }
	doQuack(){ Quackable.quack() }
}
0. The First and Second Principles of Object-Oriented Programming
Author
Aaron
Posted on
Licensed Under
CC BY-NC-SA 4.0
CC BY-NC-SA 4.0
More in this category
Recent posts
The Erosion of Conversational Muscle and Communication Styles by LLM Filters
In an era where LLM tools, which filter out conversational impoliteness and deliver refined responses, have become commonplace, are we truly engaging in more thoughtful conversations? This article examines the phenomenon of conversational ability, which should be honed through countless failures in real-time communication, degenerating due to reliance on external tools. It further explores the potential societal anxieties and shifts in generational behavioral patterns that this trend may bring.
Optimal Timing and Strategy for Salary Negotiation with Senior Candidates
Salary negotiation is more than just an exchange of figures; it's a strategic dance of psychological timing. This analysis explores why engaging in a gradual negotiation process from the initial stages of recruitment, rather than waiting until after a final offer (when candidates tend to adopt a more calculative stance), proves more efficient for companies and fosters a more honest sharing of resources.
The Limits of the Rule of Law and Human Diversity
The belief that all human actions can be regulated by a single legal system may be an act of hubris. This article offers a sharp analysis of the paradox of the rule of law faced by humanity, which, having escaped the hierarchical controls of the Middle Ages, has now embraced infinite modern freedom. It further examines the deepening social coercion and the demonization of others that arise under the guise of diversity.
토스트 예시 메세지