前置き
TLでOOPに関して盛り上がっていたのでclass設計に関してまとめてみました。
長くなったのでクラス間の関係に関してはこっちで記載しています。
オブジェクト指向設計とは
現実世界と紐付けた説明をよく見かけるのですが、そこまで壮大に考える必要はないです。
二次元に生きている大きなお友だちも安心してください。そしてリア充は爆発しろ。
動的に生成される変数に対するアクセス手段を制限し、コード全体の見通しを良くする設計技法です。
この変数とそれにアクセスできる関数をパック1したものをオブジェクトと呼ぶと、オブジェクト間の呼び出し関係でコードが書けるというステキナイスな設計しやすさ。
オブジェクト(インスタンス)
変数とそれにアクセスする関数をパックしたもの。
後述のクラスとの線引が曖昧なので特に実際に確保されたオブジェクトを指してインスタンスと呼ばれたりする。
フィールド
パックされた変数。
複数の場合もあるが、大量にある場合は設計をミスっている可能性が高い。
メソッドを介してのみアクセスが可能2である。
別名: メンバ変数、プロパティ3
メソッド
パックされた関数。
読むだけ、セットするだけはgetter/setterと呼ばれて特別扱いされたりする。
別名: メンバ関数
メンバ
オブジェクトに含まれる連中の総称。
クラス
オブジェクトのメンバ構成を記載したもの。
型解決が動的か静的かでだいぶ概念が違うので混乱の元4。
静的型付け言語では型と同一視される。
オブジェクトはクラスを元に生成される。
コンストラクタ/デストラクタ
インスタンスの生成時と破棄時に呼び出される関数。
最近の言語はGCでいい感じに破棄してくれるのでデストラクタは仕様になかったりする。
オブジェクト成立期間の前後に呼び出されるのでメソッドではない。
でもこいつもクラスについでに書いちゃうから混同しがち。困ったものだよHAHAHA。
クラス変数/クラス関数/内部クラス
すべてのオブジェクトが共有して使う連中。
オブジェクト構成要素でも無いのにクラスに入り込んでくる害悪。
クラスと名前空間を混同すると増殖しがち。
定数の宣言とアルゴリズム関数ぐらいに使用を留めないとOOPが崩壊する。
クラス設計のアンチパターン
前置きはここまでとして、実務でぶつかるイケてないコードと解決策を上げていきます。
思想はわかっても実務ではよくわからないまま書いたり、突貫の増改築でクラスが腐敗していきます。
デブクラス
メンバがやたらあるクラスです。
コード変更に際してクラスの拡張で対応を繰り返していると気づいたら超巨大クラスが爆誕しています。
大艦巨砲主義は死亡フラグです。
デブクラスは基本的にフィールドが多すぎます。
指針としてはフィールドをグループ分けしてその単位でクラスを分割します。
はーい二人組作って〜
こんな手順でリファクタかけることが多いです。
- 多数のフィールドにアクセスしているメソッドを分割する
- 各フィールドとメソッドの関係性をまとめてグルーピングする
- 各グループを別クラスのオブジェクトとしてまとめる。
フィールドが外部から変更されている
はい死刑。OOPとはなんだったのか。
メソッド経由するように呼び出し元変えてアクセッサをPrivate制限かけていきましょう。
たまにメソッドがなくフィールド全部パブリックのクラスがありますが、コレはクラスというより構造体ですね。
構造体用の構文がない言語のこの用法に限ってはありかなと思います。
コンストラクタがでかい
コンストラクタ=初期化処理と思って各種チェック機構を延々書いて、あまつさえ内部で例外投げてたりします。
やめましょう。
コンストラクタが行うのはオブジェクトの初期化処理であってシステムの初期化ではありません。
システムの初期化処理は初期化処理を担うクラスを作ってそいつのメソッドにやらせましょう。
メソッドがでかい
関数分割の要領で分解してください。
アクセスするフィールドの種別単位で分けるといい感じになります。
ニートメソッド
親クラスや自クラスのメソッド呼び出してるだけの一行メソッド。
呼び出し元を変更して消しましょう。
内部クラスを別クラスで使用している
内部クラスの扱いは難しい。
オブジェクトのメソッドに渡す引数が超でかくなってしまった際の解決策として、内部クラスのオブジェクトを渡すという使い方が考えられる。
自分にはそれ以外思いつかない。
この場合生成だけは別クラスが担う。
const a = new ClassA()
a.methodA(ClassA.InnerClass(x, y, z))
クラス変数を定数以外で使用している
やめよう。お前それグローバル変数やぞ。
別クラスのフィールドに引っ越してシステムの最初でオブジェクト生成して引き回してください。
クラス関数を外部から使用している
お前そこにいる意味ないじゃん。
グローバルスコープの関数にしてあげてください。
Javaの場合は関数ベタがきできないのでMathクラスみたいに関数集約クラス作って仲間に入れてあげてください。
コンストラクタで別クラスのコンストラクタを呼んでいる
コレは少し毛色が違ってクラス間の密結合の話になります。
長くなるのでDependency Injection
でググってください。
外部で生成してコンストラクタの引数として渡しましょう。