asatoの技術的な日常日記

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

スポンサーサイト

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

[コード進化パターン][Scala] Scala におけるコード進化パターン

Scala のバージョン 2.7.2 から、Java と Scala のコードを共存できるようになったので、昔作った Java プログラムを Scala に徐々に変換しようとしてます。

で、そのJavaからScalaの変換過程を

 「Scala におけるコード進化パターン

としてドキュメント化しようとも思ってます。
スポンサーサイト

コード進化パターン:フィールドの削除

昔から書いてる、

 Java におけるコード進化パターン

を更新。今回は「フィールドの削除」を追加。

話としては、(一見)すごく単純。単にフィールドを削除する場合がありますよ、というだけ。

コードで書くとこれだけ。
before:

public class MyClass {

private String s;
}



after:

public class MyClass {
}



難しいのは、どんな状況の時に、フィールドを削除することになるのか、 という点。

いくつかの状況は考えられる。でも、それらの状況を分析しようとしている人はそんなに多くないと思う。

たとえば、フィールドを削除するだけ、というコードの進化は普通は起こらない。フィールドはどこかで使われているはずだから、メソッドボディの変更も伴う。

コード進化パターン:要求変更からコード進化パターンへ

一つ前の記事は、Java での コード進化のパターン の一つとして「View コードの変更」があることを紹介した。

このパターンを簡単に言えば、いわゆる Model-View-Controller (MVC) のビューに関するコードを変更するということ。

何も変化がなければ、この進化パターンは起こらない。実際にどんな要求仕様の変更があったのか?


具体的に何かというと、僕が今作ってるゲーム(不思議のダンジョン系)での要求変更。一人で作っているので、僕が「ユーザ」と「開発者」の二つの役割を持ってる。

見た目の変化は次の画像からわかるように「クリティカル率」を「必殺率」に変えた点。

見た目変更前:
view_change_before.png


見た目変更後:
view_change_after.png




ユーザと開発者の仕様の変更のやりとりはこんな感じ:

ユーザ「もっとステータス画面うまく表示できないですかね? クリティカル率って他のと比べて長くないですか?」

開発者「分かりました。クリティカルじゃなくて、じゃあ、同じような意味の必殺でいいですかね? 文字数もちょうどそろいますし」。

ユーザ「それでいいんじゃないですか」



で、実際にどの部分のコードが変更されたか。StatusViewDrawer クラスがこのステータス画面の見た目を描画してる。

変更前のコード:


public class StatusViewDrawer implements GameViewDrawer {

private GameManager gameManager ;

public StatusViewDrawer(GameManager gameManager ) {
this.gameManager = gameManager;
}

public void draw(Graphics g) {

...

Player player = env.getPlayer();

drawStringWithShadow(g, "攻撃力     " + player.getAttackPoint(), baseX + 20, baseY + 30);
drawStringWithShadow(g, "防御力     " + player.getGuardPoint(), baseX + 20, baseY + 60);
drawStringWithShadow(g, "命中率     " + player.getStatus().getHitRate(), baseX + 20, baseY + 90);
drawStringWithShadow(g, "回避率     " + player.getStatus().getAvoidRate(), baseX + 20, baseY + 120);
drawStringWithShadow(g, "クリティカル率 " + player.getStatus().getCriticalRate(), baseX + 20, baseY + 150);

drawStringWithShadow(g, "経験値 " + player.getEXP(), baseX + 20, baseY + 180);

int nextLvExp = env.getPlayer().getNextExp() - env.getPlayer().getEXP();
drawStringWithShadow(g, "次のレベルまで " + nextLvExp, baseX + 20, baseY + 210);
...
}
...
}



変更後のコード:


public class StatusViewDrawer implements GameViewDrawer {

private GameManager gameManager ;

public StatusViewDrawer(GameManager gameManager ) {
this.gameManager = gameManager;
}

public void draw(Graphics g) {

...

Player player = env.getPlayer();

drawStringWithShadow(g, "攻撃力 " + player.getAttackPoint(), baseX + 20, baseY + 30);
drawStringWithShadow(g, "防御力 " + player.getGuardPoint(), baseX + 20, baseY + 60);
drawStringWithShadow(g, "命中率 " + player.getStatus().getHitRate(), baseX + 20, baseY + 90);
drawStringWithShadow(g, "回避率 " + player.getStatus().getAvoidRate(), baseX + 20, baseY + 120);
drawStringWithShadow(g, "必殺率 " + player.getStatus().getCriticalRate(), baseX + 20, baseY + 150);

drawStringWithShadow(g, "経験値 " + player.getEXP(), baseX + 20, baseY + 180);

int nextLvExp = env.getPlayer().getNextExp() - env.getPlayer().getEXP();
drawStringWithShadow(g, "次のレベルまで " + nextLvExp, baseX + 20, baseY + 210);
...
}
...
}




考察:

この要求変更の場合、運よくコードへの変更は、一つのクラスの一つのメソッドだけが対象となっている。でも、場合によっては、複数のビュークラスを変更しなければならない場合もある。現実的にはありえないけど、たとえば、次のコードのそれぞれの行に対して、クラスを割り当てている場合など。



drawStringWithShadow(g, "攻撃力 " + player.getAttackPoint(), baseX + 20, baseY + 30);
drawStringWithShadow(g, "防御力 " + player.getGuardPoint(), baseX + 20, baseY + 60);
drawStringWithShadow(g, "命中率 " + player.getStatus().getHitRate(), baseX + 20, baseY + 90);
drawStringWithShadow(g, "回避率 " + player.getStatus().getAvoidRate(), baseX + 20, baseY + 120);
drawStringWithShadow(g, "必殺率 " + player.getStatus().getCriticalRate(), baseX + 20, baseY + 150);



このことからは、モジュール(この場合はクラス)を分割しすぎると、変更がしにくくなるかもしれないことを意味する。

また、ビュー以外のコードも変更になるかもしれない。

view_code_change.png

コード進化パターン:Viewコードの変更

3年以上前から続けてる「Java におけるコード進化パターン」を更新。今回は、「Viewコードの変更」を追加してみた。

このパターンを簡単に言えば、いわゆる Model-View-Controller (MVC) のビューに関するコードを変更するということ。

今までと同じように、下の図では一般化して表してはいるけど、昨日プログラミングしているときに実際に出くわした変更(進化)のパターン。

change_view_code_before.jpg



言うのは、簡単に見える。でも、色々と疑問はある。

まず、「Viewコードの変更」ということで、View の存在を仮定してる。正確に言えば、View というクラスではなく、View という役割(ロール)を持ったクラスが存在することを仮定してる。

View の役割がどこからでてきたかといえば、既に書いたように、MVC における View を指している。

したがって、この「Viewコードの変更」は、MVC (アーキテクチャ)パターンが適用されてることを前提にした進化パターンなのかということになる。

そうすると、MVC が適用されてるかどうかを どこまで厳密に 前提とするのかが問題となる。

そのため、僕は POSA 本と PoEAA 本を見てみた。

MVC のようなアーキテクチャパターン限らず、あるアーキテクチャ・デザインパターンが適用されているのか(したのか)を判断することは、人手でも難しい。通常、パターンの記述に、あるパターンの全てのバリエーションを書くことはできない。

MVC も同様で、どこまでが MVC かを判断することは難しい、と思う。たとえば、POSA 本では、MVC の実装するとして、Publisher-Subscriber デザインパターンを用いている。しかし、僕が「Viewコードの変更」を行ったときの設計・実装では、Publisher-Subscriber は適用していない。


また、場合によっては、View と Model が分離されていないかもしれない。その場合は、もう MVC とは呼べないかもしれないけど、別の見方をすれば、あるクラスは Model と View の二つの役割を持っているともいえるかもしれない。とすると、「Viewコードの変更」パターンは、MVC 以外でも存在することになる。


一つ目の疑問をまとめると、ある進化パターンを表現する場合、どんな前提を置くかということになる。


二つ目は、「Viewコードの変更」パターンの "Viewコード" の部分。書き方としては、view メソッドとも書けたかもしれない。でも、コードとしたのは複数のメソッドを変更する場合があったかもしれないため。



コード進化パターン:列挙型への定数の追加

コード進化パターンを更新。

 Java におけるコード進化パターン

今回は、「列挙型への定数の追加」という単純なパターン。

コードの変化だけを書くなら、これだけ。

before:

public enum EnumType {
TypeA,
TypeB
}



after:

public enum EnumType {
TypeA,
TypeB,
TypeC
}




つまらない例に思えるけど、一つ議論するならば、このパターンが起こらない状況もあるってこと。どんな状況か。

sun の列挙型の例に載っているけど、たとえば、 季節を表す列挙の場合。


enum Season { WINTER, SPRING, SUMMER, FALL }



季節は普通四つしかないので、この列挙型に対する、定数の追加や削除の進化パターンはない。

この季節の例はわかりやすいけど、普通が通用しなくて、さらに進化パターンが許されていない状況もあるかもしれない。しかし、言語レベルで、許されていない進化パターンを制御する仕組みは無い。制御の仕組みがないということは、誤った進化が起こってしまうということ。

コード進化パターン:トップレベル interface の追加

Java におけるコード進化パターン」を更新。今回は、抽象クラスからなるクラス階層のトップに interface を導入するという進化の例。

コード進化パターン:抽象クラスのジェネリック化

Java におけるコード進化パターン」を更新。今回は、抽象クラスをジェネリックにするというリファクタリングのコード進化の例。

コード進化パターン:hook operationの追加

久しぶりにコード進化パターンを更新。

 Java におけるコード進化パターン
 

コード進化パターン:抽象クラスの 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ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。