privateメソッドって本当に必要?
プログラムを書いているときに、ふと考えました。
「privateメソッドのそもそも正しい使い方は何なんだろう?」って。
クラスは設計図で、人クラスのインスタンス=私だとしたら、私が公開していない機能、つまり契約とは一体何なのか。
走る、書く、話すといった公の行動はわかります。でも、privateメソッドとして隠されているものって、オブジェクト目線ではどういう意味を持つのか、正直全くイメージできませんでした。
そこで今回は、privateメソッドの正しい使い方について、自分なりに整理してみようと思います。現場でコードを書きながら感じたモヤモヤや、教科書的な理想との乖離も含めて順を追って考えていきます。
1. privateメソッドはオブジェクト目線では存在しない
よく教科書で、
「あなたは呼吸するよね、それがprivateメソッドです」
みたいな例を見かけます。でも、これ違いますよね。呼吸するのは肺の機能であって、人が肺を持っているという関係性です。ここでいう「持つもの」は、プログラム上でいうメンバー変数にあたります。肺というオブジェクトを持っていること自体は、メンバー変数として表現されるわけです。肺の機能そのものを「privateメソッド」と見なすのは、設計上の概念と現実の関係性をごっちゃにしているだけです。
つまり、privateメソッドはオブジェクトの自然な機能ではなく、外部に見せたくない内部手順をプログラマが不自然に定義したものであるように見えます。
2. 教科書的なクラス設計との乖離
教科書的には、クラスは「データとそのデータ操作をまとめて隠蔽したもの」と言われます。理想的にはこうです:
- クラスは単一責務
- publicメソッドは外部との契約
- privateメソッドはその契約を実現するための内部手順
でも、実際のコードではこれが崩れることがあります。たとえば、あるクラスにメンバー変数を操作するprivateメソッドが存在している場合です。こうしたprivateメソッドは、本来であればそのメンバー変数を持ち、同じ機能をpublicメソッドとして提供するミニクラスとして独立させるべきものです。
言い換えると、privateメソッドを作ってしまうことで、本来独立させるべきクラスの責務を1つのクラスに押し込めてしまっていることがあります。
図でイメージすると
あるクラスに複数のprivateメソッドがある場合、それぞれは本来、メンバー変数を持つミニクラスとして独立させることができます。図で表すとこんなイメージです:
+----------------------+
| BigClass |
|----------------------|
| - memberA |
| - memberB |
|----------------------|
| + doSomething() |
| - helperA() |
| - helperB() |
+----------------------+
helperA/Bは本来ミニクラスとして分離できる
3. 私の考え:privateメソッドが使われる背景
ここで整理すると、privateメソッドが使われる背景には大きく2種類があります。
(1) 設計上の不備で生まれるprivateメソッド
- 本来は別クラスに切り出すべき責務を、1つのクラスに詰め込むために作られた
- 責務がぼやけてしまい、テストや保守がしづらい
- この場合は、privateメソッドを作るのではなく、クラスを分けるべき
(2) 実装上の都合で作られるprivateメソッド
- 本当に外部に公開する必要がない内部手順
- デザインパターンの内部処理や補助計算など、設計上自然に必要
- この場合はprivateメソッドの使用は妥当で、むしろ作らないと設計が成立しない
4. 実務でのアプローチ
個人的なルールとしてはこう考えています:
-
原則としてprivateメソッドは作らず、必要な機能はミニクラスとして切り出す
- 設計上の責務を明確に保つことで、テストや保守がしやすくなる
-
単純な共通処理だけが必要な場合は、staticなprivateメソッドを作る
- オブジェクトの状態に依存しない処理はstaticにすることで再利用性やテストのしやすさが向上する
-
どうしても設計上隠蔽が必要で、外部に見せたくない内部手順がある場合にのみprivateメソッドを作る
- その前に、本当にミニクラスとして切り出せないかを確認する
コード例:レジで消費税適用
class CashRegister {
public:
// コンストラクタで軽減税率対象かどうかを設定
CashRegister(bool reducedRate = false) : reducedRate_(reducedRate) {}
double getTotal(double subtotal) {
// 小計に消費税を適用(軽減税率考慮)
return applyTax(subtotal, reducedRate_);
}
private:
bool reducedRate_;
static double applyTax(double amount, bool reduced) {
constexpr double STANDARD_TAX = 0.10; // 10% 消費税
constexpr double REDUCED_TAX = 0.08; // 8% 軽減税率
double taxRate = reduced ? REDUCED_TAX : STANDARD_TAX;
return amount * (1.0 + taxRate);
}
};
ポイント:
-
CashRegisterはオブジェクトごとに軽減税率対象かどうかの状態reducedRate_を持つ -
getTotalが public メソッドで、外部は税率の計算手順を意識せず呼び出せる -
applyTaxは内部計算手順としてstatic privateで隠蔽
5. まとめ
- privateメソッドは「設計不備」と「本当に公開不要な内部手順」の2種類に分けられる
- オブジェクトの自然な機能ではなく、プログラマが不自然に定義した内部手順
- 無駄に作らず、必要な場合だけ作る、もしくはstatic privateにする
- 設計が複雑になりそうな場合はクラス分割を検討する
この考え方を意識するだけで、コードを書いていてモヤモヤすることも減りますし、設計の透明性と保守性もぐっと上がります。