한국어 | English | 日本語
Webアプリケーションエンジニア (経験8.8年)
技術・開発
engineering
ウェブフロントエンドと バックエンド開発を扱います

0. オブジェクト指向プログラミングにおける第1、第2原則

コンポジション vs 継承
筆者の大学時代、オブジェクト指向プログラミングは多態性と継承だけでした。企業で開発を経験し深く実感し学んだことは、オブジェクト指向プログラミングは、当然ながら学問ではなく実践であるということです。デザインパターンを学習するにあたり、なぜOOPの第1、第2原則を扱うのか疑問に思うかもしれません。デザインパターンは「オブジェクト指向パラダイムにおいて、より良いコードとは何かについての考察の結果」です。これを学習する前にオブジェクト指向プログラミングの原則を知っておくことで、様々なデザインパターンの意義を正しく体得することができます。
学校でOOPを初めて学ぶ際に触れる「継承」は、学問上の順序で最初に習うというだけで、実際の産業では最も避けるべきアンチパターンです。本稿では、「継承」をどのように、なぜ避けるべきかについて、第1、第2原則を通じて説明します。

継承=オブジェクト指向プログラミング?

開発者が初めてエンタープライズオブジェクト指向プログラムを作成すると仮定してみましょう。学校で学んだ通りであれば、オブジェクト指向プログラミングはまさに継承だと教わったように、果敢に親クラスを作成し、それを継承した子クラスを活用するでしょう。コードは下記のHead-First本の例のようになるはずです。

最初に学んだ継承

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

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

継承を使用すると、親のDuckクラスにあるすべての関数を子Duckクラスがすべて持つことになります。問題点は、親から継承を受けると資産と負債を共に受け取るように、子Duckクラスは自身の意思とは関係なく、持ちたくないすべての親関数を持ったまま拡張しなければならないという点です。これは開発において不要な制約を招くことになります。

これを解決するために、「分離可能な最小単位の関数」をインターフェースとして定義し、開発しようとしているクラスに必要な関数を持つインターフェースを選択して拡張すれば良いのです。

継承の代わりにインターフェース

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

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

これにより、親クラスに属していた振る舞いをインターフェースに細分化し、実装クラスには必要な振る舞いだけを付与できるようになりました。しかし、「振る舞い単位」インターフェースを選択して拡張する際、毎回実装しなければならないという問題があります。開発者は面倒くさがりな民族ではないでしょうか。毎回実装するのが面倒なので、「振る舞い単位」インターフェースを「振る舞い単位」クラスとして事前にすべて実装しておき、その「振る舞い」を選択的に利用するという境地に達します。

インターフェースの「実装」ではなく「コンポジション(構成)」

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() }
}

毎回インターフェースを実装するのではなく、事前に実装されているインターフェースの実装体をH選択的に構成することになります。これにより、望む振る舞いインターフェースの実装を自由に付け替えたり、交換したりできるようになりました。このようにして、インターフェースは大学で習ったクラスのテンプレートであるという概念を超え、**振る舞いや特性を実装したクラスを格納できる一つの「変数」と捉えると良いでしょう。これこそが、私たちが多態性(ポリモーフィズム)**を学んだ理由でもあります。

オブジェクト指向プログラミングにおける第1、第2原則

上記で述べた内容は、結局以下の二つの原則に短くまとめられます。

「クラス - 継承」よりも「インターフェース - コンポジション」を使用せよ

「継承」は、親クラスが持つすべてを必要性に関係なく受け継ぐことになります。まるでレゴのように、望む実装を選択的に持つ「コンポジション」の方が、より高い拡張性を提供します。

「具象クラス」よりも「インターフェース」で構成せよ

実装はいつでも変更される可能性があります。クラス内のロジックを構成する際は、具象クラスではなくインターフェースを通じて柔軟に構成しましょう。

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. オブジェクト指向プログラミングにおける第1、第2原則
Author
Aaron
Posted on
Licensed Under
CC BY-NC-SA 4.0
CC BY-NC-SA 4.0
同じカテゴリーの関連記事
最新記事
LLMフィルターが奪う会話の筋肉とコミュニケーション様式
会話における無礼さを濾過し、洗練された回答を生成するLLMツールが日常化した現代において、私たちは本当に思慮深い会話をしているのだろうか?リアルタイムのコミュニケーションにおける数多くの失敗を通じて磨かれるべき会話能力が、外部ツールに依存することで退化している現象と、それがもたらす社会的な不安や世代間の行動様式の変化について考察する。
シニア採用における年俸交渉の最適なタイミングと戦略
年俸交渉は単なる数字の交換ではなく、心理的な駆け引きとタイミングが重要です。本稿では、企業側にとって、候補者が計算的な態度を取りがちな最終合格後よりも、採用プロセスの初期段階から段階的に交渉を進めることが、なぜより効率的であり、率直な情報の共有に繋がるのかを考察します。
法治主義の限界と人間の多様性
全ての人間の行為を単一の法体系で規制できるという信念は、傲慢であるかもしれない。この記事は、中世の階層的な統制から脱却し、現代の無限の自由を手に入れた人類が直面する法治主義の逆説と、多様性という名のもとに深化する社会的強制力と他者への悪魔化現象を鋭く分析する。
토스트 예시 메세지