タイトルは私のことです。
今は必要ないけど、いつか使うだろうなぁ。とおもい、いろいろ本を買ったり参考サイトを見たりしましたが、全然理解できませんでした。
「カプセル化・・・なんか当たり前のことなきがする・・・」
「○○パターン、うん、なんとなくわかるけど使いみちはいつ?先生が・・・生徒に指示を出す?どういう例だこりゃ。」
わかったような気もするけど全然わかってない状態が続きました。
最近ちょっとわかってきたので、自分のような人や、プログラムを書き始めの人、学生の方などの参考になれば嬉しいです。
クラス設計でいちばん大切なこと
オブジェクト指向設計の目的は「良い設計」をすることです。
なので以下はオブジェクト指向という言葉は使わずに、良い設計をするための説明をします。
クラス設計で大切なことはこの3でクラス設計を分けることだと思ってます。
1 生成 * クラスを生成する部分。newするところ 2 振る舞い * プログラムの実際の処理のところ。購入する、親の許可を取る、など。 3 振る舞いの呼び出し * 振る舞いを呼び出すための条件分岐。if、switchなど。
このとき、 生成を誰がどこで行うか、振る舞いの呼び出しをどのようなルールで行うか 。を考えることが、ソフトの 拡張性や堅牢性 、わかりやすさ、いわゆる良い設計 に繋がります。
この良い設計の指針が SOLIDの原則 (よく耳にしますね)に当てはまっているか、です。
クラス分けをする
int main(){ //クラスの生成①(生成) Porsche porsche; //車の名前がポルシェなら購入、それ以外ならパスする②(振る舞いの呼び出し) if (porsche.name = "Porsche") { //購入処理③(振る舞い) Buy(porsche) } else { continue; } }
Carクラスにはnameというクラス変数があるとしてください。 buy(car)はcarを買うという処理だとしてください。
この程度であればクラス分けは必要ありませんね。
しかし、こんなときはどうでしょうか
- 車種をが増え、Jaguarクラスが増えたら・・・①
- 車種が増える事により、条件分岐が増える・・・②
- 購入処理に親の許可が必要になったら・・・③
int main(){ //クラスの生成 Porche porshe; Jaguar jaguar; //車の名前がポルシェなら購入、それ以外ならパスする if (porshe.name = "Porsche") { //親の許可を取る GetPerission(); //購入処理 Buy(porshe) } else if(jaguar.name = "Jaguar") { continue; } else{ continue; } }
そういう考えられる変化に対応できるようにクラス設計を行います。
まず、生成、振る舞い、ふるまいの呼び出しに分けます。
クラスの生成・・・ポルシェとジャガーの生成部分
振る舞いの呼び出し・・・if分
振る舞い・・・親の許可、購入処理、continue(何もしない)
さて、このとき頭を悩ませるのは、 誰が何をどこまで知っているか 、です。
ここではmain君に焦点を絞りましょう。
main君が知っていること | main君が知らないこと |
---|---|
生成するクラスの名前、振る舞いの呼び出し条件、振る舞いの名前 | 振る舞いの具体的な処理 |
この、main君が知っていることを増やすと、上記のように変更時の影響量が大きくなります。
しかしメリットもあります。それはソースが単純でいられるということです。
そして知らないことを増やすと、変更時の影響量は減っていきます。「知らないこと」の中で何をされても関係ないですからね。
極端な例ですが、何でもやってくれる執事クラス(Butlar)ができたとしましょう。
int main(){
Butlar butlar;
butlar.run();
}
はい、先程の処理をすべてButlar.run()で行うようにしました。
いくらButlar内の変更が行われても、main君ができることはbutlar.run()しかないので、全く影響はありませんね。
このように誰が何をどこまで知っているべきかを考えて設計することが、その時時にあった最適な設計方針になります。
しかし、この誰が何をどこまで、が要件によってかなりの振れ幅があります。この振れ幅に合わせて設計することが非常に大切です。一番大切です。
そうしないと、あるときは最適設計だけど、あるときは過剰設計、またあるときは設計不足に陥ります。
では次の記事で、今回のmain君をクラス設計してみようと思います。
(次回記事作成中)