At line 4 changed one line |
!「privateメソッドに渡すパラメータをひとかたまり(データ構造)とするクラスを見逃している。」 |
*privateメソッドに渡すパラメータをひとかたまり(データ構造)とするクラスを見逃している |
At line 9 changed 2 lines |
[{Image src='custocompany1.png'}] |
「担当者姓」と「担当者名」という属性がこのクラスの中にはあります。そしてこの二つの値を使った「担当者氏名を返す」メソッドを作ってみます。姓と名の間にスペースを挟んで返す仕様だとすると、コードは次のようになるでしょう。 |
[custocompany1.png] |
*担当者姓 |
*担当者名 |
という属性がこのクラスの中にはあります。そしてこの二つの値を使った |
*担当者氏名を返すメソッド |
を作ってみます。姓と名の間にスペースを挟んで返す仕様だとすると、コードは次のようになるでしょう。 |
At line 19 changed 2 lines |
次に、このクラスが仕様変更となり、「代表者姓」と「代表者名」という属性が追加されたとします。「代表者氏名を返す」というメソッドもこの時必要になったとすると、以下のようなコードを追加することでしょう。 |
[{Image src='custocompany2.png'}] |
次に、このクラスが仕様変更となり、 |
*代表者姓 |
*代表者名 |
という属性が追加されたとします。\\ |
[custocompany2.png]\\ |
*代表者氏名を返すメソッド |
もこの時必要になったとすると、以下のようなコードを追加することでしょう。 |
At line 29 changed one line |
そして次に、「いずれの氏名を返す時も、姓と名の間のスペースを2つに替えて欲しい。」という要望が挙がったとします。よく見てみると上記二つのメソッドは非常に良く似ています。「スペースを挟んで姓と名を連結する」という仕様だからです。こういう時、privateメソッドを作っておくと変更が1ヶ所で済んだはずです。次のようなコードです。 |
そして次に、 |
*いずれの氏名を返す時も、姓と名の間のスペースを2つに替えて欲しい |
という要望が挙がったとします。よく見てみると上記二つのメソッドは非常に良く似ています。「スペースを挟んで姓と名を連結する」という仕様だからです。こういう時、privateメソッドを作っておくと変更が1ヶ所で済んだはずです。次のようなコードです。 |
At line 47 changed 2 lines |
[{Image src='supplyer.png'}] |
例えば「仕入れ先会社」クラスが別にあったとして、その属性にも「担当者姓」「担当者名」があったとしたらどうでしょう? そしてそのクラスにも「担当者氏名を返す」というメソッドが必要で、「姓と名の間にスペースを2つ挟む」という仕様だったら? そのクラスにも同じようなprivateメソッドが必要になってしまい、仕様変更への対応が1ヶ所というわけにいかなくなってしまいます。\\ |
例えば |
*仕入れ先会社クラス |
[supplyer.png]\\ |
が別にあったとして、その属性にも「担当者姓」「担当者名」があったとしたらどうでしょう? そしてそのクラスにも「担当者氏名を返す」というメソッドが必要で、「姓と名の間にスペースを2つ挟む」という仕様だったら? そのクラスにも同じようなprivateメソッドが必要になってしまい、仕様変更への対応が1ヶ所というわけにいかなくなってしまいます。\\ |
At line 51 changed 4 lines |
上記の例で解るように、__「privateメソッドを作りたくなった時はクラスを見逃している」__のです。\\ |
privateメソッドはクラス内の共通関数に当たるのですが、共通関数を作ろうとすれば、その関数に渡す__パラメータをそもそもまず共通化する必要があります__。そのパラメータをひとかたまり(データ構造)とするクラスを作るべきなのです。\\ |
この場合は、属性として「姓」と「名」を持つ「氏名クラス」を作るべきです。そしてそのクラスに__委譲__するのです。 |
[{Image src='name.png'}] |
上記の例で解るように、 |
*privateメソッドを作りたくなった時はクラスを見逃している |
のです。\\ |
privateメソッドはクラス内の共通関数に当たるのですが、共通関数を作ろうとすれば、その関数に渡す__パラメータをそもそもまず共通化する必要があります__。 |
*そのパラメータをひとかたまり(データ構造)とするクラスを作るべき |
なのです。\\ |
この場合は、属性として |
#姓 |
#名 |
を持つ__氏名クラス__を作るべきです。そしてそのクラスに__委譲__するのです。\\ |
[name.png] |
At line 58 changed one line |
パラメータの無い、つまり__共通関数ですらないprivateメソッド__がなぜ必要なのでしょうか? 彼らの答えは「可読性をあげるため長いコードを分割するんだ。」なのですが、私に言わせると「ちゃんちゃら可笑しいw」です。\\ |
パラメータの無い、つまり__共通関数ですらないprivateメソッド__がなぜ必要なのでしょうか? 彼らの答えは「可読性をあげるため長いコードを分割するんだ。」なのですが、これは全くの誤りであると私は考えます。\\ |
At line 110 changed one line |
正しいクラスを定義した結果、それでもメソッドの記述が長くなる場合、例えば属性が100個ぐらいあって一つの処理が長くなる場合、それはそれで正しいのです。意味も無く分割する必要は無いというのが私の考えです。 |
正しいクラスを定義した結果それでもメソッドの記述が長くなる場合、例えば属性が100個ぐらいあって一つの処理が長くなる場合、それはそれで正しいのです。意味も無く分割する必要は無いというのが私の考えです。 |
At line 113 changed 3 lines |
[クラスとはデータ構造]で説明した次の図をもう一度見て下さい。\\ |
[{Image src='http://download.oracle.com/javase/tutorial/figures/java/concepts-object.gif'}] |
フィールド(属性)の周りをメソッドが取り巻いている形が見えますが、これは裏返すと、「フィールドを隠蔽するためにメソッドが口を開けている」ということです。つまり公開されないメソッドはこの原則に反するのです。__privateによって隠蔽するべきなのはフィールドであってメソッドではない__という原則がこの図からも解ります。\\ |
[クラスとはデータ構造]で説明した次の図をもう一度見て下さい。出典: [What Is an Object?|http://download.oracle.com/javase/tutorial/java/concepts/object.html]\\ |
[{Image src='http://download.oracle.com/javase/tutorial/figures/java/concepts-object.gif'}] |
フィールド(属性)の周りをメソッドが取り巻いている形が見えますが、これは裏返すと、 |
*フィールドを隠蔽するためにメソッドが口を開けている(公開されている) |
ということです。つまり公開されないメソッドはこの原則に反するのです。 |
*privateによって隠蔽すべきなのはフィールド(属性)であってメソッドではない |
という原則がこの図からも解ります。\\ |
At line 141 changed one line |
[ユーティリティクラス禁止]でも書きましたが、このようなユーティリティクラス(インスタンス変数にもクラス変数にもアクセスしないメソッド群)はそもそもオブジェクト指向の原則である「データ構造と関数の一体化」を崩してしまいます。その結果どういう事が起きるかを考えてみます。\\ |
[関数とユーティリティクラスは禁止]でも書きましたが、このようなユーティリティクラス(インスタンス変数にもクラス変数にもアクセスしないメソッド群)はそもそもオブジェクト指向の原則である |
*データ構造と関数の一体化 |
を崩してしまいます。その結果どういう事が起きるかを考えてみます。\\ |
At line 146 changed 2 lines |
private String yuubinBangou = ""; |
private String jyuusho = ""; |
private String yuubinBangou = ""; // 郵便番号 |
private String jyuusho = ""; // 住所 |
At line 156 changed 3 lines |
StringUtility#getFullName()メソッドの機能は、「1つめのパラメータと2つめのパラメータを2個のスペースを挟んで連結する」というものです。上記のように、「事務所クラスの宛先を返すメソッド」にこの機能がたまたま使えたとすれば使われる可能性を排除できません。\\ |
この時、システムを国際化対応する必要がもしも出てきて、「担当者ミドルネーム」「代表者ミドルネーム」という属性が追加になったとします。そして、「氏名を返すメソッドは全て『姓 ミドルネーム 名』とする(間のスペースは1つずつ)」という要件に替わったとします。\\ |
この場合の変更箇所はStringUtility.getFullName()メソッドのみではなく、それを呼び出している全メソッドが対象となります(当然ですが)。その際、「従業員クラスの宛先を返すメソッド」も該当しますが、事務所クラスは「ミドルネーム」を持たないため破綻してしまいます。別の関数を新たに作る必要が出てきます。\\ |
StringUtility#getFullName()メソッドの機能は、「1つめのパラメータと2つめのパラメータを2個のスペースを挟んで連結する」というものです。上記のように、「事務所クラスの宛先を返すメソッド」にこの機能がたまたま使えたとすれば使われてしまう可能性を排除できません。\\ |
この時、システムを国際化対応する必要がもしも出てきて、 |
*担当者ミドルネーム |
*代表者ミドルネーム |
という属性が追加になったとします。そして、 |
*氏名を返すメソッドは全て『姓 ミドルネーム 名』とする(間のスペースは1つずつ) |
という要件に替わったとします。\\ |
この場合の変更箇所はStringUtility.getFullName()メソッドのみではなく、それを呼び出している全メソッドが対象となります(当然ですが)。その際、「従業員クラスの宛先を返すメソッド」も該当しますが、事務所クラスは「ミドルネーム」を持たないため破綻してしまいます。別の関数を新たに作る必要が出て来るのです。高い保守性とこれでは言えません。\\ |
At line 160 changed 4 lines |
一方で、「氏名クラス」を利用している場合はどうでしょう?\\ |
氏名クラスを利用する場合、そのデータは「姓」と「名」として使うことでしょう。「郵便番号」と「住所」を無理矢理入れようと思えば入れられますが、上記のユーティリティクラスの使われ方ほどの可能性はありません。 なぜならば、ユーティリティクラスの目的は「処理」、つまり「1つめのパラメータと2つめのパラメータを2個のスペースを挟んで連結する」という機能を提供することですが、氏名クラスの目的は__氏名となるデータを保持すること__だからです。誤用の可能性は低いでしょう。\\ |
ひとかたまりとなるデータ構造をクラスに閉じ込め、そのデータに関する処理も併せてその中に閉じ込めてしまうことで修正箇所を最小限にしようというオブジェクト指向の目的がこの例からも解ります。 |
[{Image src='name2.png'}] |
一方で、「氏名クラス」を利用している場合はどうでしょうか。\\ |
氏名クラスを利用する場合、そのデータは「姓」と「名」として使うことでしょう。「郵便番号」と「住所」を無理矢理入れようと思えば入れられますが、上記のユーティリティクラスの使われ方ほどの可能性はありません。\\ |
なぜならば、ユーティリティクラスの目的は「処理」つまり |
*1つめのパラメータと2つめのパラメータを2個のスペースを挟んで連結する |
という__機能を提供すること__ですが、氏名クラスの目的は、 |
*氏名を構成する属性(データ構造)を保持すること |
だからです。誤用の可能性は低いでしょう。\\ |
[name2.png]\\ |
氏名クラスを利用している場合、__氏名を返す()__メソッドを呼び出している側の修正は必要ありません。このことがシステムの保守性をいかに高くするかを是非理解して下さい。\\ |
ひとかたまりとなるデータ構造をクラスに閉じ込め、そのデータに関する処理も併せてその中に閉じ込めてしまうことで修正箇所を最小限にしようというオブジェクト指向の目的がこの例からも解ります。\\ |
At line 170 changed one line |
!再帰処理 |
!!再帰処理 |
At line 201 changed 2 lines |
!汎用的なデータ構造を持つクラスを継承せずに利用する |
これは、JavaのArrayListクラスのように、汎用的なデータ構造を持つクラスを継承せずに以上の形で利用した場合、その汎用的なクラスの扱いを共通化したい場合です。 |
!!汎用的なデータ構造を持つクラスを継承せずに利用する |
これは、JavaのArrayListクラスのように汎用的なデータ構造を持つクラスを継承せずに委譲の形で利用する時、その汎用的なクラスに対する特定の扱いを共通化したい場合です。\\ |
%%prettify |
{{{ |
public class Someclass { |
private String name = "XX"; |
private List<Integer> someList = new ArrayList<Integer>(); |
: |
: |
public String firstMethod() { |
// リストの初期化 |
clearList(); |
何かの処理; |
: |
} |
|
public String secondMethod() { |
// リストの初期化 |
clearList(); |
別の何かの処理; |
: |
} |
|
/** リストの要素をゼロで置き換える **/ |
private void clearList() { |
for (Integer el : someList) { |
el = new Integer(0); |
} |
} |
}}} |
/% |
この場合のprivateメソッドは、汎用的なデータ構造を持つクラスを継承するならば必要なくなります。上の例ではArrayListクラスです。ですが、汎用的に出来ているクラスの継承においては、そのクラスのデータ構造を熟知していないと思わぬ副作用に出くわすデメリットもあるため、privateメソッド利用とのデメリットをよく検討した上で選ぶようにして下さい。\\ |
|
!!まとめ |
*privateメソッドを作っていけない |
*ただし以下は例外 |
**再帰処理 |
**汎用的なデータ構造を持つクラスを継承せずに利用する場合 |
|
!!コラム |
2000年頃の話ですが、私が初めて仕事でJavaを使ったプロジェクトにて、私は帳票用の自動集計フレームワークを設計・実装していました。\\ |
そのプロジェクトの本番稼働後に仕様追加の依頼があってフレームワークのコードを直そうとしたところ、同じ修正を2ヶ所にしなければならない事が判明しました。そのフレームワークの実装に当たっては、「1ヶ所直せば皆直る」を心掛けていたのにもかかわらずです。\\ |
私は必死になって理由を考えました。そしてこのページで説明した「privateメソッドが癌だった」ことに気付いたのです。その時は新しいクラスを作ってリファクタリングし、仕様変更に無事対応できました。\\ |
オブジェクト指向の出発点は「1ヶ所直せば皆直る」です。難しいことは置いておいても、これを目指して皆さんも設計・実装してみて下さい。そうすれば、オブジェクト指向の核心がきっと見えてきます。\\ |
\\ |
次: [どのメソッドをどのクラスで実装すべきか] |