単一責任の原則(Single Responsibility Principle)
定義
SOLID原則のひとつ。 単一責任の原則について調べると以下のように記述されている。
- Every class should have only one reason ton change.
- A module should be responsible to one, and only one actor.
前者は「クラスの変更理由はひとつ」、後者は「モジュールはひとつのアクターにのみ責任をもつ」。
"クラス"と"モジュール"についてはほぼ同義と考えて良いと思う。
そうすると、上記の2つは以下のように考えられる。
- クラスはひとつのアクターの要求に答える責任を持つ
- クラスの変更理由は対象のアクターの要求が変更された場合のみ
例)カロリー計算
以下は、1日の最大摂取カロリーと減量または増量を設定。食事ごとに1日の最大摂取カロリーとの比較を行うクラス。
public class CalorieControl
{
private int MaxCalorie; // 1日の最大摂取カロリー
private bool LossWeight; // 減量する場合はtrue
private int TotalCalorie; // 1日の摂取カロリー
public CalorieControl(int maxCalorie, bool lossWeight)
{
this.MaxCalorie = maxCalorie;
this.LossWeight = lossWeight;
}
public void Eat(int calorie)
{
this.TotalCalorie += calorie;
if (this.LossWeight)
{
if (this.TotalCalorie <= this.MaxCalorie) return;
System.Console.WriteLine($"Over Calorie. {this.TotalCalorie}");
}
else
{
if (this.TotalCalorie > this.MaxCalorie) return;
System.Console.WriteLine($"Less Calorie. {this.TotalCalorie}");
}
}
}
問題点
上記のプログラムについて考えてみる。
『クラスはひとつのアクターの要求に答える責任を持つ』
"カロリー計算をしたい人"がアクターと考えると、その要求に答える責任は持っているように思える。
『クラスの変更理由は対象のアクターの要求が変更された場合のみ』
対象のアクターについては良いように思えたが、何を目的にカロリー計算をしたいのかに着目する。
対象クラスのLossWeightプロパティが該当する。
bool値であることから、2つの目的が考えられる。
Trueの場合は、減量するために"カロリー計算をしたい人"。
Falseの場合は、増量するために"カロリー計算をしたい人"。
『減量するために"カロリー計算をしたい人"』が、以下のような要求を出す。
『食品の表示カロリーと実際のカロリーには乖離があると思うので、比較する際に許容範囲を持たせたい。』
LossWeightがTrueの場合の比較部分の軽微な修正で終わりそう。
このとき、『増量するために"カロリー計算をしたい人"』は理由なくクラスが修正されている。
もう一度『クラスはひとつのアクターの要求に答える責任を持つ』
アクターは"カロリー計算をしたい人"ではなく、"減量したい人"と"増量したい人" が正しい。
そのために必要なのが"カロリー計算"(汎化)で、クラスは以下のようになる。
public class CalorieControl
{
private int TargetCalorie; // 1日の目標摂取カロリー
private int TotalCalorie; // 1日の摂取カロリー
public CalorieControl(int maxCalorie) => this.MaxCalorie = maxCalorie;
public void Eat(int calorie)
{
this.TotalCalorie += calorie;
}
}
public class WeightLoss : CalorieControl
{
public WeightLoss(int targetCalorie) : base(targetCalorie) { }
public override void Eat(int calorie)
{
base.Eat(calorie);
if (this.TotalCalorie <= this.TotalCalorie) return;
System.Console.WriteLine($"Over Calorie. {this.TodaysCalorie}");
}
}
public class WeightGain : CalorieControl
{
public WeightGain(int targetCalorie) : base(targetCalorie) { }
public override void Eat(int calorie)
{
base.Eat(calorie);
if (this.TotalCalorie > this.TotalCalorie) return;
System.Console.WriteLine($"Less Calorie. {this.TodaysCalorie}");
}
}