[雑談] 隠ぺいの有用性とセッター・ゲッターの役割
Javaでプログラムを書き始めたときに「なぜ?」と思う点の一つがセッター・ゲッターの存在だと思います。
フィールドに対して直接アクセスすればよさそうなのに、わざわざセッター・ゲッターの2個のメソッドを用意しなければならないし、オブジェクトを使う際にもいちいちセッター・ゲッターのメソッド呼び出しをしなければならないというのは面倒なうえに非効率と感じるものです。
今回はセッター・ゲッターをなぜ使うのか、それらの役割について少し考えてみたいと思います。
シナリオ: 文書記録台帳
こういった問題を考える際に実際のプログラム開発がどのように行われるか、シナリオをもとに考えていくのが問題を具体的にとらえることができていいかもしれません。
ということで文書記録台帳をプログラムとして作ることを考えてみます。
A社では様々な仕様書や設計書などの文書を作成・更新した際に文書記録台帳に文書名と作成・更新時刻を文書記録台帳に記帳することで特定の文書が更新されたにもかかわらず関連する文書の更新が行われていないといったミスを削減する運用を行っています。
この文書記録台帳を電子化することになり、以下のような文書記録 (Record) クラスを作成しました。
public class Record {
// 文書名
public String documentName;// 最終更新日時
public LocalDateTime revisedDateTime;
};
LocalDateTimeはJavaの標準ライブラリに含まれるクラスの一つで、日付、時刻を記録するものです。
文書を作成、更新した場合には以下のように文書記録クラスのオブジェクトを1個作成し、文書名と日時を記録します。
Record record = new Record();
record.documentName = "外部仕様書";
record.revisedDateTime = LocalDateTime.now();
LocalDateTime.now()は現在の日時を取得するLocalDateTimeクラスのクラスメソッドです。
このようにすれば必要な文書記録オブジェクトが生成できますので、これをどこか適切な場所に保存することで文書記録台帳とすることができます。
突然の仕様変更
A社は最近、米国に事務所を構えることになり、米国事務所で作成、更新された文書も同じ文書記録台帳に記録することになりました。
ここで困ったことが発生してしまいました。
日本の事務所と米国の事務所では時差があるため、日付時刻を見ただけではどちらが新しいかを判断することができなくなったのです。
このため、プログラムを修正しなければなりません。
修正方法をいくつか考えてみました。
日本時間に合わせて記録する
一つの解決策は米国事務所用にプログラムを作成して、プログラムの中で現地時間を日本時間に置き換えて保存するというものです。
例えばこんなプログラムになります。
Record record = new Record();
record.documentName = "外部仕様書";
Instant localInstant = Instant.now();ZonedDateTime japanTime = localInstant.atZone(ZoneId.of("Asia/Tokyo"));.
record.revisedDateTime = japanTime.toLocalDateTime();
InstantはJava標準クラスライブラリに含まれる、グリニッジ標準時 (GMT) の1970年1月1日午前0時からの経過時間で日時を表現するクラスで、Instant.now()メソッドで現在日次に相当するInstantオブジェクトを取得することができます。
ZonedDateTimeはタイムゾーン付きの日時表現で、InstantオブジェクトのatZone()メソッドを使用することで指定されたタイムゾーン、上の例ではAsia/Tokyoですが、での日時にInstantオブジェクトを変換することができます。
さらにZonedDateTimeオブジェクトのtoLocalDateTimeメソッドを使用することで日本時間のLocalDateTimeオブジェクトに変換しています。
このやり方でとりあえずは動くのですが、問題は日時を扱っているところ、文書記録の作成だけでなく表示や比較など多数あると思いますが、それぞれに同様の修正を入れないといけないという点があります。
今回は日本時間で記録するという点を変えないという方向で修正を行いましたが、例えばGMTで記録するように変更してもプログラムの修正は同様に必要ですし、先ほどのInstantのように時差の影響を受けないオブジェクト型に変更したところで修正が必要という点に変わりはありません。
セッター・ゲッターを使う
このように仕様変更に対してプログラムの修正が必要になるのは最初のプログラムが時差を意識していなかったためでした。
こういった考慮漏れ、あるいは想定外の要求はちょくちょく発生するものなので、事前に対応できる準備をしようというのがセッター・ゲッターを使う意味になります。
最初のRecordクラスをセッター・ゲッターを使用したものに書き直すと以下のようになります。
public class Record {
// 文書名
private String documentName;// 最終更新日時
private LocalDateTime revisedDateTime;
// セッター・ゲッター
public void setDocumentName(String documentName) {
this.documentName = documentName;
}
public String getDocumentName() {
return documentName;
}
public void setRevisedDateTime(LocalDateTime revisedDateTime) {
this.revisedDateTime = revisedDateTime;
}
public LocalDateTime getRevisedDateTime() {
return revisedDateTime;
}
}
そして文書記録を作成するコードは以下のようになります。
Record record = new Record();
record.setDocumentName("外部仕様書");
record.setRevisedDateTime(LocalDateTime.now());
これだけだと何が良くなったかわからないと思います。
そして、先ほどのように時差への対応が必要となったときにはRecordクラスは以下のようになります。
public class Record {
// 文書名
private String documentName;// 最終更新日時: 日本時間で記録する
private LocalTime revisedDateTime;
// セッター・ゲッター
public void setDocumentName(String documentName) {
this.documentName = documentName;
}
public String getDocumentName() {
return documentName;
}
public void setRevisedDateTime(LocalDateTime revisedDateTime) {
// システムのタイムゾーンを参照してタイムゾーンの付いた日時に変換する
ZonedDateTime localTime = revisedDateTime.atZone(ZoneId.systemDefault());
// 日本のタイムゾーンが付いた日時に変換する
ZonedDateTime japanTime = localTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
// 日本のタイムゾーンが付いた日時をLocalDateTime型に変換して記録する
this.revisedDateTime = japanTime.toLocalDateTime();
}
public LocalDateTime getRevisedDateTime() {
// 記録されている日本時間のLocalDateTimeをタイムゾーンの付いた日時に変換する
ZonedDateTime japanTime = revisedDateTime.atZone(ZoneId.of("Asia/Tokyo"));
// システムのタイムゾーンを参照して現地のタイムゾーンが付いた日付に変換する
ZonedDateTime localTime = japanTime.withZoneSameInstant(ZoneId.systemDefault());
// 現地時間のタイムゾーンが付いた日時をLocalDateTime型に変換して返す
return localTime.toLocalDateTime();
}
}
コメントに詳細は記載してありますが、セッター・ゲッターで現地時間と日本時間を変換することで記録されている最終更新日時は日本時間、読み書きする日時は現地時間となるのでRecordオブジェクトを使用するプログラムは変更が不要となり、記録されている日時も従来と同様に日本時間になるので、変更前のデータも継続して利用できます。
このようにセッター・ゲッターを使用することで実装の詳細、この例で言うと記録している日時の仕様を隠すことができるので、仕様変更への対応を一つのオブジェクトに閉じた形で実現することも可能となります。
もちろんすべての仕様変更に耐えられるわけではありませんが。
将来起きるかもしれない変更を見据えた場合、内部でのデータ保持形式など実装の詳細を隠蔽し、公開された外部仕様だけにのみアクセスできるようにすることは重要で、セッター・ゲッターは隠蔽において重要な役割を果たすものになります。
セッター・ゲッターにはほかにもいくつかの有用な機能がありますが、それはまたそのうち考えていきたいと思います。
0コメント