asatoの技術的な日常日記

「成長に最大の責任をもつ者は、本人であって組織ではない。自らと組織を成長させるためには何に集中すべきかを、自らに問わなければならない」  非営利組織の経営 - ピーター・ドラッカー

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

コード進化パターン:抽象クラスの Template Method 化

コード進化パターン」のシリーズ。

今回にパターンは「抽象クラスの Template Method 化」という内容。名前から分かるように、Template Method パターンに関係する内容。

名前付けは、ちょっと間違えた気がするけど。

とりあえず、進化前のコードはこんな感じ:


abstract_class_to_template_method_before.jpg


public abstract class AbstractClass {

public void method() {
System.out.println("AbstractClass.method()");
}

// 単なる抽象メソッド。AbstractClass が抽象クラスであることを
// 表すために定義。進化の観点からは関係なし。
public abstract void abstractMethod();
}
public class ConcreteClass extends AbstractClass {

@Override
public void method() {

super.method(); // ここが重要。

System.out.println("XXX");
}

@Override
public void abstractMethod() {
System.out.println("ConcreteClass.abstractMethod()");
}
}




進化後はこんな感じ:
abstract_class_to_template_method_after.jpg




public abstract class AbstractClass { // AbstractClass のロール

public void method() { // templateMethod のロール

System.out.println("AbstractClass.method()");

primitiveMethod();
}
// primitiveOperation のロール。抽象メソッドでもよい。
protected void primitiveMethod() { }


public abstract void abstractMethod();
}
public class ConcreteClass extends AbstractClass {

// method() はオーバーライドせずに、
// この primitiveMethod() をオーバーライドする。
@Override
protected void primitiveMethod() {
System.out.println("XXX");
}


@Override
public void abstractMethod() {
System.out.println("ConcreteClass.abstractMethod()");
}
}



コードから分かるように、サブクラスでオーバーライドできるように単に primitive メソッドを追加して、template メソッドからそれを呼び出すようにしただけ。

考察


進化の流れから、考察を行ってみたい。前回 ちょっと議論したように、進化のパターンを議論するにあたって重要となりそうな概念の一つに、進化のリクエスタ/プロバイダというのがあると思う。

進化リクエスタ とは、ある構成要素(コンポーネント、あるいはモジュール、クラス)に対して、何らかの構造的変化を要求する構成要素。

進化プロバイダ とは、進化リクエスタからの要求に応じて、自身の構造を変化させる構成要素。

上の「抽象クラスの Template Method 化」の例で言えば、ConcreteClass が進化リクエスタであり、AbstractClass が進化プロバイダとなる。
req_pro.png


図からわかるように、2つのフェーズがあるんじゃないかと思う。名前付けは、悩んだけど、適切ではないかもしれない。
-(1)要求フェーズ:ある構成要素が、他の構成要素に対して、構造的変化を要求するフェーズ。要求する構成要素を、進化リクエスタと呼ぶ。要求に応答する構成要素を、進化プロバイダと呼ぶ。

ここで、要求に答えられない可能性もある。たとえば、外部のライブラリに対しては、変化を要求することは難しい。

-(2)提供フェーズ:進化リクエスタからの要求に答えて、進化プロバイダは、構造を変化させる。

-(3)応答フェーズ:進化プロバイダの構造変化に応答して、進化リクエスタは構造を変化させる。

疑問


いくつかの疑問がある。
-(1)原理・原則との関係:進化リクエスタは、要求を出す、ということを仮定していた。そもそも、その要求はどんな原理・原則に基づいて行われるのか?

GoF 本では、Template Method パターンの適用可能性の一つとして次を挙げている。

サブクラスの拡張を制御する場合。特定の時点で "hook" operation を呼び出すテンプレートメソッドを定義することができる。それにより、このポイントでのみ拡張が許されることになる。


この「抽象クラスの Template Method 化」という進化パターンは、この適用可能性として起こったのか? うまくは答えられないけど、No だと思う。

あるいは別の記述では(太字は僕による):


サブクラスは、オペレーションをオーバーライドしたり、親のオペレーションを明示的に呼び出すことにより、親クラスのオペレーションの振る舞いを拡張することができる。

void DerivedClass::Operation() {
// DerivedClass extended behavior
// ParentClass::Operation();
}

しかし残念なことに、継承したオペレーションを呼び出すことで拡張するということは忘れやすい。そこで、そのようなオペレーションを template method に変換して、サブクラスがそのオペレーションをどのように拡張するのかを親クラスに管理させるようにすることができるのである。その場合、template method から hook operation を呼び出すようにしておき、サブクラスでこの hook operation をオーバーライドする。

~省略~



太字にした部分が、進化の動機になる要素の 一つ だと思う。ただし、この引用文からは、「継承したオペレーションを呼び出すことで拡張するということは忘れやすい。したがって、あらかじめ、メソッドを template method にしておく」というということを感じる。

しかし、あらかじめ、つまり予測して設計することは
-(1)困難
-(2)不適切に複雑
な設計になりうる危険がある。

つまり、Template Method パターンが適用されていない状況(進化前)から、Template Method パターンが適用されている状況(進化後)に導く状況と、その理由が存在すると思う。

-(2)進化関連の種類とパターン:考察の部分で紹介した、進化リクエスタ/プロバイダのやり取りは、一つの種類でしかないと思う。たとえば、
-(1)進化リクエスタが複数ある場合があるかもしれない。
-(2)進化プロバイダは、要求にこたえるために、自身が進化リクエスタとなって他の構成要素になんらかの進化の要求を行うかもしれない。
スポンサーサイト

コード進化パターン:Event Object への get method の追加

久々に、コード進化のパターンを考えるシリーズの続き。パターンカタログは ここ を参照。

今回は「Event Object への get method の追加

簡単に言えば、Listener パターン(Observer パターンの一種)が適用された構造への変化の例。特に、Event Object に対する変化の例。

進化前のコードはこんな感じ:


add_get_method_to_event_object_before.jpg


public class ComponentEvent {

private String eventInfo1;

public ComponentEvent(String eventInfo1) {
this.eventInfo1 = eventInfo1;
}

public String getEventInfo1() {
return eventInfo1;
}
}

public interface ComponentListener {
public void eventOccurred(ComponentEvent e);
}

public class MyComponentListenerA implements ComponentListener {

public void eventOccurred(ComponentEvent e) {
System.out.println("event info1: " + e.getEventInfo1() );
}
}

public class Component {

private List listeners = new ArrayList();

public void addComponentListener(ComponentListener l) {
listeners.add(l);
}

public void removeComponentListener(ComponentListener l) {
listeners.remove(l);
}

public void method() {

System.out.println("before evnet");

fireEventOccurred( new ComponentEvent("aaa") );

System.out.println("after evnet");
}

private void fireEventOccurred(ComponentEvent e) {

for(ComponentListener l : listeners) {
l.eventOccurred(e);
}
}
}






変更後はこんな感じ:



add_get_method_to_event_object_after.jpg


public class ComponentEvent {

private String eventInfo1;
private int eventInfo2;

public ComponentEvent(String eventInfo1, int eventInfo2) {
this.eventInfo1 = eventInfo1;
this.eventInfo2 = eventInfo2;
}

public String getEventInfo1() {
return eventInfo1;
}

public int getEventInfo2() {
return eventInfo2;
}

}

public interface ComponentListener { // 変更なし

public void eventOccurred(ComponentEvent e);
}
public class MyComponentListenerA implements ComponentListener { // 変更なし

public void eventOccurred(ComponentEvent e) {
System.out.println("event info1: " + e.getEventInfo1() );
}
}

public class MyComponentListenerB implements ComponentListener { // 新規追加

public void eventOccurred(ComponentEvent e) {
System.out.println("event info2: " + e.getEventInfo2() );
}
}


public class Component {

private List listeners = new ArrayList();

public void addComponentListener(ComponentListener l) {
listeners.add(l);
}

public void method() {

System.out.println("before evnet");

fireEventOccurred( new ComponentEvent("aaa", 111) );

System.out.println("after evnet");
}

private void fireEventOccurred(ComponentEvent e) {

for(ComponentListener l : listeners) {
l.eventOccurred(e);
}
}
}




考察


考察としては、以下がある。

-(1)進化のリクエスタ:この例の場合、進化のリクエスタには、MyComponentListenerB が対応する。進化のリクエスタとは、ある構成要素(クラス)に、進化を要求する役割を持ったクラス。たとえば、MyComponentListenerB は、eventInfo2 の情報が必要だったので、ComponentEvent の進化、つまり getEventInfo のメソッドの追加を要求した。

まだ全ての場合を検証していないけれど、コードの進化には、進化のリクエスタみたいな関係があると思う。

一つ、この関係を考えるのが重要なのは、クラスのインタフェースは、誰かからの要求によって進化していくということ。これは、どういう意味か。進化した部分は、進化のリクエスタにとって必要だったから、ということ。別の言い方をすれば、進化した部分は、他のクラスにとっては関係がない進化だったということ。
evolution_requester.png


すべてのコード進化には、このような進化リクエスタ/プロバイダの関係を伴うのかというと、そうでもない。
evolution_requester2.png


分類すると以下が考えられる:
-進化リクエスタ/プロバイダ関係による進化:コード(サービス)は、リクエスタからの要求に答えられるように進化する。

-プロバイダによる進化:リクエスタからの要求がなくても、プロバイダはサービスを提供するように進化する。言い換えれば、あらかじめ進化リクエスタからの要求を予測した進化。

-インタラクションの進化:サービスの呼び出し(あるいは依存)関係が進化する。

-サービスの進化:サービスの仕様が進化する。

-その他の進化:それら以外の進化。


-(2)進化パターン関係:ある進化のパターンがその他の進化のパターンを伴っている場合がある。たとえば「Event Object への get method の追加」パターンは「ConcreteListenerの追加」パターンに伴って起こることがある。上記のコード例で言えば、MyComponentListenerB が追加されたことにより、ComponentEvent に getEventInfo2 の get method を追加する必要が出てきた。

evo_pattern_use.png


まとめ


「Event Object への get method の追加」という進化パターンを紹介した。このパターンから進化パターンの種類を考察した。また、進化パターン間の関係を考察した。

FC2Ad

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。