はじめに
オブジェクト指向において、クラスではなくインターフェースのを使うときに、よく「抽象に依存する」といわれている気がします。AIに聞いてもこんな感じで説明された覚えがあります。
ここで私はこう思いました。
「抽象に依存」って抽象的過ぎるだろ!!
抽象に依存するという言葉自体が抽象的すぎてよくわかりませんでした。
実際のコードを見たら、なんとなくインターフェースが有用なことはわかります。
- 複数のクラスをひとまとめに表せる
- クラスの機能を保証する
などなど。まあなんとなくいろんなクラスを同じリストや配列にぶち込んで同じように使えることは有用に感じます。
そんでもって、確かになんとなくクラスより抽象的になってる感は理解できるかもしれません。
しかしながら、具体的にクラスを使うときと比べてどれぐらい差があるのかがわからず、「実はそんな変わらないんじゃないか」と思っていました。
本記事では、そんな抽象的な言葉と具体的なコードの間ぐらいの抽象度で、インターフェースに対する有用性の解釈をまとめます。
結論
インターフェースは、「同じ役割」という括りにのみ依存するもの
これが私の出した最終的な解釈です。
普通のクラスとの違いを明確にするために、クラスへの依存についても載せます。
クラスは、「データの構成」、「ロジック」へ依存するもの
といった感じになります。
「データの構成」、「ロジック」とは?
まずクラスから。
クラスはそもそも、データとロジックをひとまとまりにしているものです。
そのため、「クラスに依存する」とは「データとロジックに依存する」ことの言い換えであることはわかりやすいと思います。
public class Player{
int hp;
...
int GetAttack(){
return 10;
}
void TakeDamage(int attack){
hp -= attack;
}
...
}
public class Enemy{
int hp;
int deffence
...
int GetAttack(){
return 10 + (int)(deffence * 1.2);
}
void TakeDamage(int attack){
hp -= attack - deffence;
}
...
}
public Battle{
...
void Attack(Player player, Enemy enemy){
int playerAttack = player.GetAttack();
enemy.TakeDamage(playerAttack);
}
...
}
ここでは、BattleクラスのAttackメソッドでPlayerとEnemyというクラスを使っています。
PlayerクラスとEnemyクラスは少し似ていますが、ロジックとデータともに少しずつ違うクラスです。
Battleクラスの入力では、「第一引数はPlayerっていうデータとロジックじゃないといやだ!」、「第一引数はEnemyのデータとロジックじゃないとダメ!」と言っているようなものです。
役割的に似てるけど、似ていようが何だろうが同じじゃないとダメ!!
クラスに依存するということはこういうことなんじゃないかと思います。
そのため、BattleクラスのAttackメソッドで、「同じようなものなんだからいいじゃん」と、PlayerとEnemyを入れ替えて入力することは許されていないのです。
Player player = new Player();
Enemy enemy = new Enemy()
Battle battle;
battle.Attack(player, enemy); //正常に動く
battle.Attack(enemy, player); //動かない
「同じ役割」とは?
では、インターフェースはどうでしょうか。
インターフェースは別々のクラスに対して同じ名前のメソッドを実装させます。
interface ICharacter{
void GetAttack();
void TakeDamage(int attack);
}
public class Player extends ICharacter{
int hp;
...
public void TakeDamage(int attack){
playerHp -= attack;
}
...
}
public class Enemy extends ICharacter{
int hp;
int deffence
...
int GetAttack(){
return 10 + (int)(deffence * 1.2);
}
void TakeDamage(int attack){
hp -= attack - deffence;
}
...
}
public Battle{
...
void Attack(ICharacter attacker, ICharacter damager){
int attackerAttack = attacker.GetAttack();
damager.TakeDamage(attackerAttack);
}
...
}
ここでは、BattleクラスのAttackメソッドの入力はインターフェースになっています。
前述のコードでは、PlayerとEnemyは似ているけど、ロジックもデータも少しずつ違うという説明をしました。
「確かに少しだけ違うかもしれないけど、どちらも『ダメージを受ける』ことと『攻撃力を見せる』役割は同じなんだから、同じように扱わせてよ!」
これを実現できるものがインターフェースです。
仕組みは違うけど役割が同じであればなんでもいいから使いたい。
これは、「役割に依存する」ということができると、私は考えます。よってインターフェースは、
インターフェースは「同じ役割」に依存するもの
であると考えます。
役割という抽象的なモノに依存することによって、AttackメソッドではPlayerとEnemyを逆に入力しても正常に動作するようになりました。
ICharacter player = new Player();
ICharacter enemy = new Enemy()
Battle battle;
battle.Attack(player, enemy); //正常に動く
battle.Attack(enemy, player); //正常に動く
まとめ
インターフェースを使うことは役割に依存することです。
「役割」は、確固たるデータやロジックと比較して、抽象的な概念になります。
そのため、インターフェースを使うとき「抽象に依存する」といわれることが多いわけですね。
実際、「データやロジックがこれじゃないとダメ!」みたいな場合ってあんまない気がするんですよね。
DTOとかRepositryで考えられはしますが、データを使って何かやりくりする場面では別にそこまで要求されないような気がしてます。
じゃあ全部インターフェース使えばいいじゃん!!!
クラスじゃないといけない!!!という具体的な状況に会ったことのある有識者の方は、ぜひ教えてください!