3分間プログラミング講座 第3回: クラスの「責任」って?~単一責任の原則~
登場人物:
チャット先生(通称:先生): オブジェクト指向マスター。普段は優しく、たまにユーモアも交えながら解説。
ボット助手(通称:ボット君): プログラミングを始めたばかりの若手。素朴な疑問を投げかけるのが得意。
ボット君: 先生、この間言ってた「責任の分担」って、すごく気になります!クラスに何でもかんでも詰め込んじゃいけない、ってどういうことなんですか?
先生: やあ、ボット君!いいところに気がついたね。まさにそれが、今日のテーマ「クラスの『責任』と単一責任の原則」だよ。オブジェクト指向設計で、とても大切な考え方なんだ。
ボット君: 「責任」ですか?クラスが責任を持つなんて、なんだか人間みたいですね!
先生: その通り!プログラミングの世界でも、クラスにはそれぞれ「どんな仕事をするべきか」という『責任』があるんだ。前回のCarクラスを例に考えてみようか。
クラスの「責任」とは?
先生: ボット君、前回のCarクラスは、どんな「責任」を持っていると思う?つまり、どんな仕事をすることが期待されているかな?
ボット君: えーと、「色」や「メーカー」のデータを持って、「加速する」とか「止まる」とか、車の基本的な動きをすること、ですか?
先生: その通り!Carクラスの主な責任は、「車の基本的な状態(データ)を管理し、基本的な動き(行動)を実行すること」だと言えるね。
先生: じゃあ、もしこのCarクラスに、「音楽を再生する機能」や「ナビゲーションを行う機能」、さらには「ガソリンの残量を管理する機能」まで、全部詰め込んでしまったらどうなると思う?
ボット君: うーん……機能が増えて便利になるんじゃないですか?
先生: たしかに一見そう見えるかもしれない。でも、もし「音楽再生機能」にバグが見つかったらどうなる?
ボット君: あ!Carクラスを修正しなきゃいけなくなりますね。
先生: そうだね。それだけじゃない。
コードがすごく長くなって、どこに何が書いてあるか分かりにくくなる。まるで、あらゆる家電の取扱説明書が1冊にまとまった本みたいにね。
「車を走らせる部分」と「音楽を再生する部分」みたいに、全く関係ないはずの機能の修正でも、同じCarクラスを開いて触ることになる。間違って他の部分を壊してしまうリスクも高まるんだ。
もし、次に「電車」のクラスを作るときに「音楽を再生する機能」が必要になったらどうする?Carクラスの音楽再生機能だけを再利用するのが難しくなるだろう?
ボット君: うわー、想像するだけで混乱しそうです……。関係ない部分を触って壊しちゃうのは怖いな。
単一責任の原則(Single Responsibility Principle / SRP)
先生: その通り。だからこそ、オブジェクト指向には「単一責任の原則(Single Responsibility Principle - SRP)」という、とても大切な考え方があるんだ。
ボット君: たんいつせきにんのげんそく?
先生: そう。簡単に言うと、「一つのクラスは、変更される理由を一つだけ持つべきだ」という原則のことだ。つまり、そのクラスが担当する「仕事」や「関心事」は一つに絞るべき、ということだね。
ボット君: クラスの責任は一つだけ……。じゃあ、「音楽を再生する機能」は、別のクラスにするってことですか?
先生: その通り!
先生: ちなみに、何でもかんでも一つのクラスに詰め込んでしまうクラスのことを、プログラミングの世界では「God Object(神オブジェクト)」と呼ぶことがあるんだ。
ボット君: 神オブジェクト?すごい名前ですね!
先生: 「神様のように何でもできる」という皮肉を込めた名前なんだ。でも実際は、管理が大変で問題の多いアンチパターン(悪い設計パターン)として知られているんだよ。
先生: 悪い例と良い例をコードで見てみようか。
Java
// 悪い例:責任が混在しているCarクラス
public class Car {
// 車の基本情報
String color;
String maker;
int speed;
// 音楽プレイヤー機能
String currentSong;
boolean isPlaying;
// ナビゲーション機能
String destination;
String currentLocation;
// 燃料管理機能
int fuelLevel;
// 車の動作
public void accelerate(int speed) { /* 加速処理 */ }
public void brake() { /* 停止処理 */ }
// 音楽機能
public void playMusic(String song) { /* 音楽再生処理 */ }
public void stopMusic() { /* 音楽停止処理 */ }
// ナビ機能
public void setDestination(String dest) { /* 目的地設定処理 */ }
public void calculateRoute() { /* ルート計算処理 */ }
// 燃料機能
public void refuel(int amount) { /* 給油処理 */ }
public boolean isFuelLow() { /* 燃料残量チェック */ }
}
ボット君: うわー、確かにこれは長くて、何のクラスなのか分からなくなりそうですね!
先生: そうだろう?これを単一責任の原則に従って分けると、こうなるんだ。
Java
// 良い例:責任を分離したクラス設計
public class Car {
String color;
String maker;
int speed;
public void accelerate(int speed) { /* 加速処理 */ }
public void brake() { /* 停止処理 */ }
}
public class MusicPlayer {
String currentSong;
boolean isPlaying;
public void playMusic(String song) { /* 音楽再生処理 */ }
public void stopMusic() { /* 音楽停止処理 */ }
}
public class NavigationSystem {
String destination;
String currentLocation;
public void setDestination(String dest) { /* 目的地設定処理 */ }
public void calculateRoute() { /* ルート計算処理 */ }
}
public class FuelTank {
int fuelLevel;
public void refuel(int amount) { /* 給油処理 */ }
public boolean isFuelLow() { /* 燃料残量チェック */ }
}
先生: そして、これらの分離したクラスはこんな風に組み合わせて使うことができるんだ。
Java
// 分離後のクラスの使用例
public class CarSystem {
public static void main(String[] args) {
// それぞれが独立したクラス
Car car = new Car("赤", "トヨタ");
MusicPlayer player = new MusicPlayer();
NavigationSystem nav = new NavigationSystem();
FuelTank tank = new FuelTank();
// それぞれが独立して動作
car.accelerate(50); // 車の責任
player.playMusic("お気に入りの曲"); // 音楽の責任
nav.setDestination("東京駅"); // ナビの責任
tank.refuel(30); // 燃料の責任
}
}
「責任の分担」のメリットと判断基準
先生: こうやって責任を分担することで、それぞれのクラスがシンプルになり、何をしているクラスなのかが明確になる。その理解でパーフェクトだ!単一責任の原則を守ることで、たくさんのメリットが生まれるんだ。
保守性の向上: 問題が発生した時に、どのクラスが原因かを特定しやすくなり、修正が楽になる。
拡張性の向上: 新しい機能を追加したい場合も、既存のクラスを大きく変更することなく、新しい責任を持つクラスを追加すればいい。
再利用性の向上: それぞれの責任が明確だから、「音楽を再生する機能」だけを別のプログラム(例えば、「家の中の音楽プレイヤー」クラス)で再利用しやすくなる。
可読性の向上: 各クラスがシンプルで、何をするクラスなのかが分かりやすくなるため、他の人がコードを読んだり、自分自身が後で見返したりするときに理解しやすい。
ボット君: すごい!単一責任って、ただ機能を分けるだけじゃなくて、プログラム全体をより良くするための考え方なんですね!
先生: その通り!大きな問題も、小さく分けてそれぞれの専門家(クラス)に任せることで、全体としてうまくいくようになるんだ。これはプログラミングだけでなく、現実の世界でも共通する考え方だよね。
先生: 身近な例で考えてみよう。スマートフォンのアプリはどうかな?「カメラアプリ」に「電卓機能」や「音楽プレイヤー機能」を全部入れたらどうなると思う?
ボット君: うーん、確かにおかしいですね!カメラアプリなのに、なんで電卓が?って感じです。
先生: その通り!それぞれ別のアプリにして、必要な時だけ使うのが自然だよね。プログラムのクラスも同じ考え方なんだ。
先生: じゃあ、ボット君。どうやって「この機能は同じクラスに入れていいか、別のクラスにするべきか」を判断すればいいと思う?
ボット君: うーん……関係があるかどうか、ですか?
先生: いい視点だね!簡単な判断基準を教えよう。
変更理由の確認:「車の速度を変える仕様変更」と「音楽の再生方式を変える仕様変更」は、全く違う理由で発生するよね?それぞれのクラスは、その「変更される理由」を一つだけ持つべきなんだ。
関心事の分離:「車を動かすこと」と「音楽を再生すること」は、本質的に異なる関心事だよね?異なる「関心事」は、異なるクラスに担当させるべきなんだ。
独立性の確認:音楽プレイヤーが壊れても車は走れるし、車が故障しても音楽は再生できるべきだよね?お互いが独立して機能できるか、という視点も重要だよ。
こういった観点で考えると、責任を分けるべきかどうかが見えてくるんだ。
今日のまとめ!
クラスの「責任」:そのクラスが「どんな仕事をするべきか」という役割のこと。
単一責任の原則(SRP):「一つのクラスは、変更される理由を一つだけ持つべきだ」というオブジェクト指向の重要な原則。
God Object(神オブジェクト):何でもかんでも一つのクラスに詰め込んでしまった、管理が大変で問題の多いアンチパターン。
メリット:
保守性の向上:修正箇所が分かりやすく、デバッグが楽になる。
拡張性の向上:新機能の追加が容易になる。
再利用性の向上:クラスの部品化が進み、他の場所でも使いやすくなる。
可読性の向上:コードが分かりやすくなる。
判断基準:「変更される理由が異なるか」「関心事が分離できるか」「独立して機能できるか」などを考えると、責任を分けるべきかどうかが判断できる。
ボット君: 先生、今日は「クラスの責任」と「単一責任の原則」について、すごくよく分かりました!何でもかんでも一つのクラスに詰め込むと、後で大変なことになるんですね!ちゃんと役割分担をすることが大事なんだ!
先生: その調子だ、ボット君!この「単一責任の原則」は、オブジェクト指向設計の基本中の基本であり、非常に強力な指針となる考え方だよ。次回は、こうやって責任を分けたクラス同士が、どうやって安全にやり取りするかについて学んでいこう。「内部の秘密は隠して、必要な部分だけを外に見せる」という「カプセル化」の考え方だよ。
ボット君: カプセル化!また面白そうです!楽しみです!ありがとうございました!
次回:第4回: クラスの「秘密」と「窓口」~カプセル化~
いいなと思ったら応援しよう!
もしこの記事が少しでも役に立ったり、楽しんでいただけたりしたら、チップをいただけるととても嬉しいです!
いただいたチップは、今後のより良いコンテンツ制作のための書籍代📚とnote購入🗒️、他の方へのチップ代💰に使わせていただきます。
いつも応援ありがとうございます!