久しぶりに@ITのサイトに行ってみた。コラムニストの皆さん相変わらずですね。
「飛田ショウマの憂鬱」
http://el.jibun.atmarkit.co.jp/pressenter/2017/01/_11.html
リーベルGさんの小説って「高慢と偏見」以来読んでいません。読みだすと憂鬱になってしまいそうで恐いです。
「インターフェース」「抽象クラス」「デザインパターン」ってオブジェクト指向の中でも高度な知識が要求される言葉が出てきます。ほんとうに実際の開発の現場で有効に役に立ってるのでしょうか?開発ツールやフレームワーク自体の開発を行うならば、そのようなフレームワーク技術は役に立つでしょう。しかし、建設会社向けのアプリケーションの開発でフレームワーク技術が有効に活用されるとは思えません。
以下のサイトでは、実際の開発会社が使っているデザインパターンの情報が掲載されています。
http://isol.pro-s.co.jp/news/2008/11/05/design-pattern/
Template Methodパターンは確かに基底クラス、派生クラスの関係で、派生クラスから基底クラスのメソッドをオーバーライドする手法で、デザインパターンを知らない人でもオーバーライドやった経験があるかもしれません。ファイルの読み込みのカスタマイズに使っていると記載されている。ある程度の効果は期待できますが、何もそのようなものを作らなくても既存のファイルアクセスライブラリで十分よ!という人もいるわけで、このパターンを活用するかどうかは開発者の趣味に依存するとことが大かと思います。このサイトでは、「リソース削減」の目的でパターンを使っていると記載されていますが、適切なテーブル構造で、適切にSQLを利用していれば、最近のリレーショナル・データベースでは高いパフォーマンスが得られます。レガシーなデータ構造で大量のデータがある場合は、移行も大変です。思ったようなパフォーマンスが出ない場合は独自にクラスを作ることになるかもしれません。
Iteratorパターンの説明読んでもさっぱりわからんので、ちょっと勉強してみました。
Iteratorパターン
http://www.itsenka.com/contents/development/designpattern/iterator.html
このパターンの肝は以下の一行。
class Client : Iterator it = a.iterator();
a は ConcreteAggregateのインスタンス
a.iterator()を実行すると以下が実行される。
ckass ConcreteAggregate メッソッド iterarator
return new ConcreteIterator(this);
thisはこのメソッドを実行するクラスのインスタンス、すなわち a
さらに このコンストラクタ ConcreteIterator(this) を実行すると以下が実行される。
class ConcreteIterator コンストラクタ
ConcreteIterator(ConcreteAggregate concreteAggregate) {
this.concreteAggregate = concreteAggregate;
}
このコンストラクタの引数は手順①で指定された a
class ConcreteIterator のメンバー変数concreteAggregateに、a のインスタンスが入る
最終的にclass ConcteIterator の concreteAggregateのメンバー変数にインスタンス a が設定されるので、ConcteIteratorのメソッドhasNext() やnext() を実行することができるようになる。
Iterator it = a.iterator();
は
Iterator it = new ConcreteIterator(a);
でもいいはずなんですが、そう替えちゃうと見え見えのパターンですかね。コンストラクターやメソッドの引数としてインスタンスを渡すのはよくるパターンですね。これじゃ月並みすぎますよね。
a.iterator() というメソッドを実行し、そのメソッドでは this に インスタンス a が入るはずなんで、それで ConcreteIteratorのインスタンス生成をする!なんてトリッキーなことをやってます。
何も配列に関する処理を複数のクラスに分散させて、さらにインスタンスの引渡しを一見してわからなくするようなデザインが実践で役に立つとは思えませんが・・・
皆さん何やってるかわからんからデザパタってすごいって思っちゃってるんでしょうか?
「飛田ショウマの憂鬱」を呼んで、俺はデザインパターンがわからないからダメだあ~なんて悲観することはないです。また時間をかけてデザインパターンを読み解く必要もないと思います。それよりはもっと実践で役に立つ勉強に時間を割り当てたほうがいいですよ。
ところで・・・
再利用って何だ?
今までどこのブログを見てもオブジェクト指向における再利用についてしっくりと説明されているものはなかった。
先ほど、List配列に関する処理が分散されていることで実装上のデメリットがあると述べたが、実はメリットもある。
オブジェクト指向特有の実装方法に継承というものがあり、共通の処理を基底クラスに収め、それを継承することで、既定クラスのコードの冗長化をなくすことができる。手続化言語におけるサブルーチンや関数による共通ロジックの冗長化防止とは違った意味で、オブジェクト指向言語はコードの冗長化を防止することができる。
継承により共通部分を基底クラスにして、複数の派生クラスを実装する・・・つまり基底クラスを再利用することになる。
その他に方法があるか?
それが委譲ともいえる実装方式・・・
引用したIteratorパターンでは、ConcreteIteratorクラスがひとつだけであったが、さらに
ConcreteIteratorZ
ConcreteIteratorGT
などのクラスを追加することがでます。しかも、それらのクラスでは、ConcreteAggregateで実装された処理をConcreteIteratorと同様に利用することができるのです。
おそらくこれがGoFの意図した再利用ではないかと・・・・
どうでしょうかねえ。
[参考にさせていただいたコード]
public interface Iterator {
public abstract boolean hasNext();
public abstract Object next();
}
public class ConcreteIterator implements Iterator {
private ConcreteAggregate concreteAggregate;
private int index = 0;
public ConcreteIterator(ConcreteAggregate concreteAggregate) {
this.concreteAggregate = concreteAggregate;
}
public boolean hasNext() {
if (index < concreteAggregate.getSize()) {
return true;
} else {
return false;
}
}
public Object next() {
return concreteAggregate.getItemAt(index++);
}
}
public interface Aggregate {
public abstract Iterator iterator();
}
public class Client {
public static void main(String[] args) {
ConcreteAggregate a = new ConcreteAggregate(); ①
a.addItem(new Item("A")); ②
a.addItem(new Item("B"));
a.addItem(new Item("C"));
a.addItem(new Item("D"));
Iterator it = a.iterator(); ③
while (it.hasNext()) {
Item item = (Item) it.next(); ④
System.out.println(item.getName());
}
}
}
A. 基底クラス(インターフェイス) Iterator、 派生クラス ConcreteIterator
B. 基底クラス(インターフェイス)Aggregate、派生クラス ConcreteAggregate
[実行手順]
① クラスConcreteAggregateインスタンス a の生成
② クラスConcreteAggregateのメソッド実行
③ クラスConcreteIteratorのインスタンス生成および ①で生成されたインスタンス a の引渡し
④ クラスConcreteIteratorのメッソッド実行、これは①で生成されたインスタンス a に対する操作となる
また参考にさせていただいた開発会社のサイトでOvserverパターンも言及していましたね。
Observeパターン
http://www.itsenka.com/contents/development/designpattern/observer.html
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public abstract class Subject {
private List<Observer> observers = new ArrayList<Observer>();
private Random random = new Random();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
Iterator<Observer> it = observers.iterator();
while (it.hasNext()) {
it.next().update(this); 実行しているクラスのインスタンスをObserverクラスに引き渡す
}
}
public int getRandomNumber() {
return random.nextInt(100);
}
public abstract int getStatus();
public abstract String getName();
public abstract void run();
}
public class ConcreteSubjectA extends Subject {
private String subjectName = "ConcreteSubjectA";
private int status = 0;
public int getStatus() {
return status;
}
public String getName() {
return subjectName;
}
public void run() {
int randomNumber = 0;
randomNumber = getRandomNumber();
if (status < randomNumber) {
System.out.println(subjectName + "の状態が " + status + " -> " + randomNumber + " に変わります。");
status = randomNumber;
notifyObservers(); ここで実はOververクラスにインスタンスを引き渡している
}
}
}
public class ConcreteSubjectB extends Subject {
private String subjectName = "ConcreteSubjectB";
private int status = 100;
public int getStatus() {
return status;
}
public String getName() {
return subjectName;
}
public void run() {
int randomNumber = 0;
randomNumber = getRandomNumber();
if (status > randomNumber) {
System.out.println(subjectName + "の状態が " + status + " -> " + randomNumber + " に変わります。");
status = randomNumber;
notifyObservers();
}
}
}
public abstract class Observer {
public abstract void update(Subject subject);
}
public class ConcreteObserver extends Observer {
private String observerName = "ConcreteObserverA";
public void update(Subject subject) {
System.out.println("観察者:" + observerName + System.getProperty("line.separator") + "通知者:"
+ subject.getName() + System.getProperty("line.separator") + "状態 :" + subject.getStatus());
}
}
public class Client {
public static void main(String[] args) {
Observer ob = new ConcreteObserver(); ①
Subject sb1 = new ConcreteSubjectA(); ②
Subject sb2 = new ConcreteSubjectB();
sb1.addObserver(ob); ③
sb2.addObserver(ob);
for (int i = 0; i < 50; i++){
sb1.run(); ④
sb2.run();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("■■ END ■■");
}
}
基底クラス:Subject、派生クラス:ConcreteSubjectA,ConcreteSubjectB
基底クラス:Observer、派生クラス:ConcreteObserver
[実行手順]
① ConcreteObserverのインスタンス生成
② ConcreteSubjectA,ConcreteSubjectBのインスタンス生成
③ ②で生成させたConcreteSubjectA,ConcreteSubjectBのインスタンスに①で生成させたConcreteObserverのインスタンスを渡す
④ ConcreteSubjectA,ConcreteSubjectBのメソッドrunの実行
ここで クラスSubject の notifyObservers(); が実行させる。
it.next().update(this) の this はそのメッソッドを実行させているインスタンス,
すなわちConcreteSubjectA または ConcreteSubjectB のインスタンスとなる。
実際にクラス ConcreteObserver のupdate メソッドの引数は、 ConcreteSubjectA, Bの基底クラスSubject であるので、 ConcreteSubjectA, Bのインスタンスを受け取り、「通知者」「状態」を受け取ったインスタンスのメソッド、getName()、getStatus()より読み取ることができる。
このパターンでも it.next().update(this) のような、thisによるメソッドを実行しているクラスのインスタンスの受け渡しの手法が用いられている。
継承やポリモーフィズムを使ったコードを読みなれていない読者もいると思いますが、派生クラス内では基底クラスのメンバー変数とメソッドを使えると思ってくれれば十分だと思います。
この例ですと、ConcreteSubjectA, ConcreteSubjectBはSubject のメソッド notifyObservers() を使ってますよね。
デザインパターンのでは、staticメソッドを使っているものもありますね。
Stateパターン
http://www.itsenka.com/contents/development/designpattern/observer.html
public interface State {
public abstract void stateMethod1(Context context, int condition);
public abstract void stateMethod2(Context context);
}
public class ConcreteStateA implements State {
private static final String stateName = "ConcreteStateA";
// -「Singleton」パターンを適用 -------------------------------------------
private static State concreteStateA = new ConcreteStateA();
private ConcreteStateA() {}
public static State getInstance() {
return concreteStateA;
}
// ------------------------------------------------------------------------
// 「Context」が参照しているアクティブな「State」オブジェクト変更用メソッド
public void stateMethod1(Context context, int condition) {
if (condition == 1) {
context.setState(ConcreteStateB.getInstance()); staicメソッドの呼び出し
System.out.println("☆☆☆☆☆ 状態変更 A -> B ☆☆☆☆☆");
}
}
public void stateMethod2(Context context) {
context.contextMethod3(" 状態:" + stateName);
}
}
public class ConcreteStateB implements State {
private static final String stateName = "ConcreteStateB";
// -「Singleton」パターンを適用 -------------------------------------------
private static State concreteStateB = new ConcreteStateB();
private ConcreteStateB() {}
public static State getInstance() {
return concreteStateB;
}
// ------------------------------------------------------------------------
// 「Context」が参照しているアクティブな「State」オブジェクト変更用メソッド
public void stateMethod1(Context context, int condition) {
if (condition == 0) {
context.setState(ConcreteStateA.getInstance()); staticメッソッドの呼び出し
System.out.println("★★★★★ 状態変更 B -> A ★★★★★");
}
}
public void stateMethod2(Context context) {
context.contextMethod3(" 状態:" + stateName);
}
}
public class Context {
private State state = null;
public Context() {
state = ConcreteStateA.getInstance();
}
public void setState(State state) {
this.state = state;
}
public void contextMethod1(int condition) {
state.stateMethod1(this, condition);
}
public void contextMethod2() {
state.stateMethod2(this);
}
public void contextMethod3(String msg) {
System.out.println(msg);
}
}
import java.util.Random;
public class Client {
public static void main(String[] args) {
Random rand = new Random();
Context context = new Context(); ①
int temp = 0;
int condition = 0;
for (int i = 0; i < 10; i++) {
System.out.println("--------------------");
temp = rand.nextInt(10);
System.out.println(i + "回目:" + temp);
condition = temp % 2;
System.out.println(" :" + condition);
context.contextMethod1(condition); ②
context.contextMethod2(); ③
System.out.println();
}
}
}
基底クラス: state、派生クラス:ConcreteStateA, ConcreteStateB
単体クラス Context
[実行手順]
① Contextのインスタンス生成
② stateMethod1の実行 Contexの変数stateにConcreteStateAまたはBのインスタンスを受け渡す
③ stateMethod2の実行 ConcreteStateAまたはBのstateNameの表示
ContexのsetStateメソッドを実行することにより、変数stateにConcreteStateAまたはBのインスタンスを渡し、制御を行っています。
ConcreteStateAまたはBのインスタンスの生成は、getInstance()というstatic関数を使っています。
staticメソッドは一部のオブジェクト指向信者に忌み嫌われている風潮ありますが、このようにデザインパターンでもちゃんと使われてるんですよ。
実際のところstateパターンのように、インスタンスを派生クラス間で頻繁に切り替えることってないんじゃないかと思いますが・・・・staticメソッドでもインスタンス生成ができますって言いたかっただけなのかなあ。