At line 1 added 27 lines |
2016-05-17 に加筆しました。 |
!!関数を作ってはいけない |
__関数__というのは、Cなどの手続き型言語で言う関数のことです。\\ |
関数を正確に表現すると、 |
*クラス変数にもインスタンス変数にも一切アクセスしないメソッド |
です。次の例が関数です。\\ |
{{{ |
/* 姓と名を基に氏名を返す */ |
public String getFUllName(String surName, String givenName) { |
return surName + " " + givenName; |
} |
|
/* 消費税額を返す */ |
public BigDecimal getSalesTaxf(BigDecimal amount) { |
BigDecimal tax = new BigDecimal(5); |
return tax.multiply(amount).divide(100, RoundingMode.HALF_UP); |
} |
}}} |
|
クラス変数にもインスタンス変数にも一切アクセスしないということは、 |
*そのオブジェクトが持つ__データ構造に依存しない__ |
ことになります。\\ |
*クラスとはデータ構造 |
という原則にこれは明らかに反します。そして、 |
*関数を作ってしまうとデータ構造と処理が分離される |
ことになり、保守性が下がってしまいます。\\ |
|
At line 2 changed 5 lines |
ユーティリティクラスと世間で言われるクラスを作ってはいけません。ユーティリティクラスというのは単なる関数やサブルーチンの集まりであり、データ構造を持たないクラスだからです。\\ |
ユーティリティクラスというのをもう少し詳しく言うと、「__クラス変数にもインスタンス変数にも一切アクセスしないメソッドを集めたクラス__」のことです。クラス変数にもインスタンス変数にも一切アクセスしないということは、データ構造を内部に持たないということです。\\ |
「クラスとはデータ構造」という原則に、これは明らかに反します。このようなユーティリティクラスを作ってしまうとデータ構造と処理が分離されてしまい、保守性が下がってしまいます。\\ |
\\ |
ユーティリティクラスの弊害については「[privateメソッド禁止]」の中で詳細に説明しているので参照して下さい。\\ |
関数を作ってはいけないのだから、 |
*ユーティリティクラスと世間で言われるクラスも当然作ってはいけない |
ことになります。ユーティリティクラスというのは関数の単なる集まりであり、データ構造を持たないクラスだからです。\\ |
ユーティリティクラスと関数の弊害については「[privateメソッド禁止]」の中で詳細に説明しているので参照して下さい。\\ |
At line 8 changed 5 lines |
!!例外的にユーティリティクラスを許す場合 |
次の二つのケースではユーティリティクラスを例外的に許します。 |
#メイン関数(メソッド)を持つクラス |
#別メモリ空間で稼働するシステムとの間でオブジェクトが持つデータを受け渡す場合 |
例外的にとは書きましたが、これらの事例は割と多く存在します。 |
!!例外的に関数を許す場合 |
次の3つのケースでは例外的に関数を許します。 |
#メイン関数 |
#異なるクラスを同一の処理で扱いたい場合 |
#別メモリ空間で稼働するシステムのオブジェクトが持つデータを受け取る場合 |
例外的にとは書きましたが、これらの事例は多く存在します。 |
At line 14 changed 3 lines |
!メイン関数を(メソッド)持つクラス |
OSがアプリケーションを起動する場合、メイン関数が起点となります。OSは、起動パラメータをデータとしてメイン関数に渡します。この起動パラメータ(データ)とメイン関数(処理)は分離されざるを得ません。そのため例外となります。\\ |
メイン関数の中には必要最低限の処理のみを記述するようにし、業務用クラスのインスタンスに処理を早く委譲すべきです。\\ |
!!メイン関数 |
OSがアプリケーションを起動する場合、メイン関数が起点となります。OSは、起動パラメータをデータとしてメイン関数に渡します。この時、 |
*起動パラメータ(データ) |
*メイン関数(処理) |
の二者は、プログラムの外部から与えられるもの(パラメータ)とプログラム処理として分離されざるを得ません。そのため例外となります。\\ |
メイン関数の中には必要最低限の処理のみを記述するようにし、 |
*業務用クラスのオブジェクトに処理を早く委譲すべき |
です。\\ |
At line 18 changed one line |
!別メモリ空間で稼働するシステムとの間でオブジェクトが持つデータを受け渡す場合 |
!!異なるクラスを同一の処理で扱いたい場合 |
At line 52 added one line |
!1.何かのメディア(媒体)に読み書きしたい場合 |
At line 41 changed 2 lines |
の2つが分離されます。この__「相手側のシステムとの間で受け渡す処理」をユーティリティクラス(関数)として実装する__必要が出てきます。相手に渡せるのはデータのみであるため、そのデータをオブジェクトから受け取って渡す処理、つまり関数が別に必要だからです。\\ |
これをクラス図に描くと次のようになります。RDBユーティリティクラスが、受注伝票や発注伝票の属性値(データ)のみを使ってRDBMSとの間で受け渡しを実施します。RDBMSは、アプリケーションが稼働しているメモリ空間の外にあります。 |
の2つが分離されます。つまり、 |
*相手側のシステムとの間で受け渡す処理を関数として実装する |
必要が出てきます。相手に渡せるのはデータのみであり、 |
*そのデータをやりとりする処理を独立させた方が効率が良い場合が多い |
からです。\\ |
これをクラス図に描くと次のようになります。RDBユーティリティクラスが、受注伝票や発注伝票の属性値のみ(データ)をRDBMSとの間で受け渡しします。アプリケーションが稼働しているメモリ空間の外にRDBMSはあります。\\ |
At line 81 added 20 lines |
Javaの場合、RDB関数の最下層には__「JDBCドライバ」(Java Database Connectivity Driver)__が配置されます。 |
しかし、受注伝票や発注伝票の中に「RDBに書き込む」「RDBから読み込む」ようなメソッドを持ち、その中からRDB関数(群)を利用することによって、受注伝票や発注伝票を扱うプログラムクラス側からはデータ構造と処理が一体化されている正しいクラスとして扱えます。次のような形です。\\ |
[{Image src='utility_class7.png'}] |
\\ |
__O/Rマッパ(Object Relation Mapper)__がJDBCドライバの上位に配置されることが実際の開発では多いのですが、そのO/RマッパがRDBストレージの代理として表現されることによって、オブジェクト指向により近い実装になります。O/RマッパがJDBCドライバを隠蔽することにより、受注伝票や発注伝票などからは__RDBMSオブジェクトに処理を委譲している__形を取ることが出来ます。 |
[{Image src='utility_class8.png'}] |
\\ |
!2.異なるクラスをComparatorによって比較したい場合 |
JavaのAPIで提供されているjava.util.Comparatorインタフェースのcompare()メソッドは関数です。 |
%%prettify |
{{{ |
public int compare(T o1, T o2) |
}}} |
/% |
o1およびo2オブジェクトのクラスそのものが__java.lang.Comparableインタフェース__を実装していれば、Comparatorインタフェースによる比較は必要ありません。しかし次のような時があります。\\ |
*Comparableインタフェースを実装していないオブジェクトを比較したい |
*比較したいオブジェクトが異なるクラスである |
上記のような場合は自作のComparatorを作る必要が出て来ます。次のようなイメージです。\\ |
[utility_class9.png]\\ |
この項で説明した関数は、オブジェクト指向の前身である「構造化言語」の特徴を引き継いだものです。このページのタイトルは「関数禁止」となっていますが、それは安易に関数を量産してしまうことを戒めているのであって、上記のように理解した上で使うことは問題ありません。\\ |
At line 45 changed 2 lines |
!!「層」が増えるほどユーティリティクラスが必要となる |
前項で説明したように、外部のメモリ空間で稼働しているシステムとのやりとりが発生する境界ではデータ構造を持たないユーティリティクラス(関数)が必要になります。これは言い方を換えると、「RDB層」「アプリケーション層」などのような__層(Layer)があればその境界線でユーティリティクラスが必要になる__ということです。次の図を見て下さい。\\ |
!!別メモリ空間で稼働するシステムのオブジェクトが持つデータを受け取る場合 |
前項で説明したように、外部のメモリ空間で稼働しているシステムとのやりとりが発生する境界では、オブジェクトではなくデータのみをやりとりする必要があります。これは言い方を換えると、 |
#RDB層 |
#アプリケーション層 |
などのような |
*層(Layer)があればその境界線で関数が必要になる |
ということです。次の図を見て下さい。\\ |
At line 48 changed 3 lines |
上の図は、一般的な3層構造アプリケーションの動きのうち、画面の情報をRDBに格納するまでの流れを表しています。層と層の間はオブジェクトをそのまま渡せないため、電文という形でデータを渡しています。電文を受け取るプロセスは各層での処理の起点の役割、つまりメイン関数と同等の機能を負います。そのため関数にならざるを得ないのです。例外の1番目で書いたのとほとんど同じ理由です。\\ |
例えばアプリケーション層の起点ではHTTP電文を受け取ってオブジェクトへと変換します。この動きをメイン関数と比較すれば、起動パラメータがHTTP電文と替わるだけであることが解ります。\\ |
結果として、上記のような__層分けを出来るだけ行わない方が本来のオブジェクト指向に近づけます__。「データと処理の分離」の発生頻度が減るからです。\\ |
上の図は、一般的な3層構造アプリケーションの動きのうち、画面の情報をRDBに格納するまでの流れを表しています。層と層の間はオブジェクトをそのまま渡せないため、 |
*電文という形でデータ構造を渡す |
ようになっています。電文を受け取るプロセスは、 |
*各層での処理の起点の役割、つまりメイン関数と同等の機能 |
を負います。そのため関数にならざるを得ないのです。例外的に関数を許す場合の1番目で書いたのとほとんど同じ理由です。\\ |
例えばアプリケーション層の起点ではHTTP電文を受け取ってオブジェクトへと変換します。この動きをメイン関数と比較すれば、__起動パラメータがHTTP電文と替わるだけ__であることが解ります。Javaサーブレットを使ったWebアプリケーションの場合、サーブレットそのものがこの関数になっています。 |
次の例は__javax.servlet.http.HttpServlet__の__doPostメソッド__ですが、サーブレットアプリケーションにおいてリクエストを電文として受け取る処理の起点になります。レスポンスを戻り値としてはいませんが、引数として受け取ったHttpServletResponseオブジェクトに対して返りの電文(データ)を出力する形の関数を実装することになります。\\ |
{{{ |
protected void doPost(HttpServletRequest req, HttpServletResponse resp) { |
|
} |
}}} |
|
結果として、 |
*層分けを出来るだけ行わない方が本来のオブジェクト指向に近づける |
のです。「データと処理の分離」の発生頻度が減るからです。\\ |
|
!!まとめ |
*クラス変数にもインスタンス変数にもアクセスしない処理(つまり関数)を作ってはいけない |
*ただし以下は例外 |
**メイン関数 |
**異なる複数のクラスを同列視したい場合 |
**「層」の間 |
|
!!コラム |
このページを読んで、「ユーティリティクラス無しで実際の開発が出来るわけがない」と感じた人は多いでしょう。裏返すとそれは、オブジェクト指向的でない実装が現場でいかに蔓延しているかを表していることになります。\\ |
ユーティリティクラス無しで実装するためには以下の二種類のクラスを見逃さずに設計することが必須です。\\ |
*[小粒クラス|小粒クラス] |
*[リンゴ一山(ひとやま)クラス|リンゴ一個とリンゴ一山は異なるクラス] |
小粒クラスは、システムをまたがった再利用を促進します。一度作れば色々な業務で利用でき、保守性を向上させます。\\ |
リンゴ一山クラスは、同じ処理があちこちで実装される危険性を排除します。\\ |
\\ |
これらのクラスをしっかりと設計すれば、「関数とユーティリティクラスっていらないんだ」と気付くことでしょう。\\ |