110.取引先名クラス
初回:2020/11/18
修正:2020/11/21
1.イノウーの憂鬱 (25)
P子「水曜日もパクパクの日?」(※1)
いいえ。さすがにパクパクだけで食っていける世の中ではありません。リーベルGさんの『イノウーの憂鬱 (25) クラス、例外、ロガー、型定義』で、Javaのクラス定義のお話が出てきました。ちょっと面白い話題なので、深堀してみたいと思います。なので、本文の小説とは全く関係しませんので、ご了承ください。
さらに、コーディングに関しては、良い・悪いはありません。あるのは、判りやすい(つまり誤解やミスにつながりにくい)のか、保守がしやすい(将来的に修正などがある場合、安全かつ迅速に対応できるか)の観点から各組織にとってより良い方法を選択するだけです。不毛な宗教戦争を仕掛けるつもりはありません。
まずは、今回の元となるクラスを再現してみます。オリジナルは『こちら』です。
/**
* 項目名:取引先
*/
public class TorihikisakiMei {
// 取引先名
private String torihikisakiMei;
public String getTorihikisakiMei() {
return torihikisakiMei;
}
public void setTorihikisakiMei(String torihikisakiMei) {
this.torihikisakiMei = torihikisakiMei;
}
}
2.クラス名は、ローマ字表記か英語表記か
取引先名だけを管理するクラスを作るのか?という疑問に関しては、ここでは触れないでおきましょう。私は作りませんけどね。
まずは、クラス名をローマ字表記にするか英語表記にするかを統一する必要があります。英語表記が『おしゃれ』かもしれませんが、すべての開発者が英語が得意とは言えません。例えば、取引先を英語表記する場合、色々と悩むかもしれません。
business partner | 取引先(広い意味での) |
customer | お客様 |
client | 顧客 |
supplier | 供給者 |
vendor | 売り手 |
supplier | 供給者 |
contractor | 請負業者 |
agent | 代理人 |
dealer | 売り手 |
distributor | 配達者 |
broker | 仲介業者 |
business associate | 仕事関係者 |
《参考資料》
https://www.niugnep.net/english-torihikisaki/
意外と難しい?取引先を英語でどう表現するか
そういう意味では『TorihikisakiMei』というクラス名はあながち間違いとは言い切れません。
P子「何でもかんでも、Customerにするという手もあるわよ」
私は何でもかんでもCustomer派ですね。どうせ英語なんて分かんないので。
3.クラス作成の第一歩(イミュータブル)
私がクラス設計する場合、一番最初に考えるのは、オブジェクトを変更不可に出来ないか、という事です。イミュータブル(変更不可)なオブジェクトは、一度作成すると決して変更されませんし、マルチスレッドで使用しても安全です。また、同じオブジェクトを使いまわせるという事はメモリの節約にもなります。
P子「所で、一番最初に考えるのはインターフェースだって、いつも言ってなかった?」
今回、その話まで混ぜると先が見えなくなるので保留にしておきます。まずは、クラス設計以前の『心構え』みたいな所から入りたいと思います。
/**
* 項目名:取引先(イミュータブル)
*/
public class TorihikisakiMei {
// 取引先名
private final String torihikisakiMei;
public TorihikisakiMei(final String torihikisakiMei) {
this.torihikisakiMei = torihikisakiMei;
}
public String getTorihikisakiMei() {
return torihikisakiMei;
}
}
元のソースと比べて、セッターメソッドが省略されており、コンストラクターで値をセットしてインスタンス変数はfinal宣言されているため、torihikisakiMeiは書き換え不可能です。ただし、このクラスのままでは、 hashCode() や equals(Object) メソッドをオーバーライドしていないため、同じ取引先のオブジェクトを別々に生成すると、Mapやequalsメソッドで別のオブジェクトと判定されてしまいます。つまり、これだけではStringオブジェクトで取引先名を作成している方が、よほどましという事です。通常は、toString()メソッドもオーバーライドしておきます。
final TorihikisakiMei tori1 = new TorihikisakiMei("ちゃとらん"); final TorihikisakiMei tori2 = new TorihikisakiMei("ちゃとらん"); System.out.println("1:"+tori1+" 2:"+tori2
+" = "+tori1.equals(tori2)); final String tori3 = new String("ちゃとらん"); final String tori4 = new String("ちゃとらん"); System.out.println("3:"+tori3+" 4:"+tori4
+" = "+tori3.equals(tori4)); ================================== 1:TorihikisakiMei@270421f5 2:TorihikisakiMei@52d455b8 = false 3:ちゃとらん 4:ちゃとらん = true
4.シングルトン・パターン
4.フライウェイト・パターン(修正:2020/11/21)
不変クラスが本領を発揮するのは、オブジェクトを使いまわすシーンです。そのためには、クラスをコンストラクターで生成するのではなく、staticのファクトリメソッドで生成させます。イミュータブル(変更不可)なオブジェクトなので、スレッドセーフなので使いまわしが可能です。イミュータブルにするなら、シングルトンも考慮すべきです。
(修正:2020/11/21)
ここでは、イミュータブル+シングルトンで作成されたインスタンスをstaticなMapで再利用する例を挙げておきます。実際のプロジェクトなら、共通なインターフェースを定義してファクトリクラスから動的に生成するかもしれませんが、ここでは簡易的に自身にファクトリメソッドを記述しておきます。
/**
*項目名:取引先(イミュータブル+シングルトン・パターン)
* 項目名:取引先(イミュータブル+ライウェイト・パターン(修正))
*/
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;
public class TorihikisakiMei {
// 取引先名
private final String torihikisakiMei;
private static final ConcurrentMap<String,TorihikisakiMei>
TORI_MAP = new ConcurrentHashMap<>();
private TorihikisakiMei(final String torihikisakiMei) {
this.torihikisakiMei = torihikisakiMei;
}
public static final TorihikisakiMei
newInstance(final String torihikisakiMei) {
return TORI_MAP.computeIfAbsent(
torihikisakiMei , mei -> new TorihikisakiMei( mei ) );
}
public String getTorihikisakiMei() {
return torihikisakiMei;
}
}
final TorihikisakiMei tori1 = TorihikisakiMei.newInstance("ちゃとらん"); final TorihikisakiMei tori2 = TorihikisakiMei.newInstance("ちゃとらん"); System.out.println("1:"+tori1+" 2:"+tori2
+" = "+tori1.equals(tori2)); final String tori3 = new String("ちゃとらん"); final String tori4 = new String("ちゃとらん"); System.out.println("3:"+tori3+" 4:"+tori4
+" = "+tori3.equals(tori4)); ================================== 1:TorihikisakiMei@6f79caec 2:TorihikisakiMei@6f79caec = true 3:ちゃとらん 4:ちゃとらん = true
これで、マルチスレッドで使っても安全ですし、同じ名称であれば、同じインスタンスが返されます。本来なら、hashCode() , equals(Object) , toString()メソッドもオーバーライドしておきます。たかだか、取引先名を管理するのに、こんなに大げさなクラス設計が必要なのか?という疑問はごもっともです。つまり、取引先名『だけ』を管理すること自体が不毛という事です。
通常なら、取引先をクラス設計するなら、取引先IDをユニークキーとして、名称やその他の情報(住所や電話番号、担当者や取引先区分=購入先なのか販売先なのかなど)も管理するでしょう。また、これらのデータは、通常データベースを利用するでしょう。データベースからデータを取り出し、Javaのオブジェクトにマッピングし、業務ロジックをオブジェクト指向で設計し、データを検索して画面に出したり、計算したり、値を書き換えたりします。住所や担当者は当然変更される情報ですが、オブジェクトとして管理している期間であれば、十分に長いと考えられますので、イミュータブル設計しても問題ないでしょう。
リレーショナルデータベースなら、データが表形式で格納されています。これをオブジェクトに変換し、オブジェクト間で関連付けた後で表形式に変換して画面に表示して、値の書き換えなどを行った後でオブジェクトの値を変更し、再びばらしてデータベースに格納する。ORマッピングなどのツールで、ある程度は相互変換を行う事が可能でしょうが、はっきり言って無駄以外の何物でもないと思っています。
この話をすると、色々と揉めるので別の機会にきっちり行いたいと思います。
ほな、さいなら
======= <<注釈>>=======
※1 P子「水曜日もパクパクの日?」
P子とは、私があこがれているツンデレPythonの仮想女性の心の声です。
コメント
匿名
書いてるのは取引先名クラスのことなのに題名が取引先クラスなのはなぜ?
匿名
なんか話がごっちゃになってない?
いのうーが指摘したのはフィールドレベルでクラスを作ってることであって、
このコラムで書いてることと噛み合ってないんじゃないか?
取引先マスタにある取引先名や取引先区分とかにそれぞれクラスを作り、中にgetter/setterだけなんて組み方はありえないし、
普通に技術力低いと言わざるを得ないと思うけど
リーベルGさんの小説を誤読してるやつは技術力はしらんが読解力は低い
ちゃとらん
10:29の匿名さん、コメントありがとうございます。
ご指摘通り、おっしゃる通りでございます。
『取引先名クラス』ですね。
ちゃとらん
12:17の匿名さん、コメントありがとうございます。
『なので、本文の小説とは全く関係しませんので、ご了承ください。』と、一応お断りは入れていたつもりです。
> 中にgetter/setterだけなんて組み方はありえないし
まったく、その通りだと思います。
小説の中で、評価Dのベンダーが作ったクラスなんで、そもそもこれをいくら改造してもダメダメなのは、理解しています。
といって、ここで本格的な取引先クラスをコーディングしても仕方がないので、さわりだけ書いてみたつもりです。
さらに、暗に、Python では不可能な final 宣言によるクラス設計という感じにしてみたつもりでした。
参考にならなかったようですみません。
匿名
4の実装はSingletonではなくFlyweightかと思います
ちゃとらん
匿名さん、コメントありがとうございます。
Factory クラスを作ってないので、単純な Singleton のつもりでしたが、newInstance で、自身がFactory クラスを兼ねていると考えると、Flyweight パターンと言えるんですね。
目的がインスタンスの使いまわしや、生成コストの軽量化であるなら、Flyweight パターンという方が、理にかなっているみたいです。
色々と勉強になります。