asatoの技術的な日常日記

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

スポンサーサイト

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

コード進化パターン:抽象クラスの継承によるコードの重複の削除

コード進化のパターンを考えるシリーズの四回目ぐらい。

本元のドキュメントはこちら。

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

今回は「抽象クラス継承によるコードの重複の削除」という内容。

簡単に言えば、抽象クラス継承すればコードの重複なくせるのでやろう、というリファクタリング。

before:
extends_abstract_class_in_refactoring_before.jpg



public class ConcreteComponentA {

private String name;

public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void methoA() { }
}

public abstract class AbstractComponent {

private String name;

public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

public class ConcreteComponentB extends AbstractComponent {
public void methodB() { }
}



after:
extends_abstract_class_in_refactoring_after.jpg


public abstract class AbstractComponent {

private String name;

public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

public class ConcreteComponentA extends AbstractComponent {

public void methoA() { }
}

public class ConcreteComponentB extends AbstractComponent {
public void methodB() { }
}


このパターンで重要なのは、納期のプレッシャーとかの理由のために、抽象クラスを継承し忘れていた、という条件。なので、before の図では、ConcreteComponentB はすでに AbstractComponent を継承している形になってる。リファクタリング本 の「スーパークラスの抽出」でいえば、AbstractComponent はまだ存在しないし、そのため ConcreteComponentA も ConcreteComponentB も AbstractComponent を継承していない。

もう一つ重要なのは、サブタイピングが進化の動機付けになっていない、という点。抽象クラスを継承する理由は、コードの重複を削除することであって、ConcreteComponentX を AbstractComponent として扱いたいわけではない。やりたいのは、実装の継承であって、型の継承ではない。


以下は、メニューを表現するクラスの具体的な現実の例。
before:

public abstract class AbstractMenu { // AbstractComponent

private int index = 0;
private int menuItemCount;

protected AbstractMenu(int menuItemCount) {
this.menuItemCount = menuItemCount;
}

public void initIndex() {
index = 0;
}

public void nextMenuItem() {

index++;
if (index >= menuItemCount) {
index = 0;
}
}
public void prevMenuItem() {

index--;
if (index <= -1) {
index = menuItemCount - 1;
}
}
public int getSelectedItemIndex() {
return index;
}
}

public class OpeningMenu extends AbstractMenu { // ConcreteComponentB

public OpeningMenu() {
super(3);
}
public boolean isScenarioModeSelected() {
return getSelectedItemIndex() == 0;
}
public boolean isDungeonModeSelected() {
return getSelectedItemIndex() == 1;
}
public boolean isEndGameSelected() {
return getSelectedItemIndex() == 2;
}
}
public class ItemUseMenu { // ConcreteComponentA

private int index = 0;
private int menuItemCount = 2;

public void initMenuIndex() {
index = 0;
}
public boolean isUseMenuSelected() {
return index == 0;
}
public boolean isDeployMenuSelected() {
return index == 1;
}
public void nextMenuItem() {

index++;
if (index >= menuItemCount) {
index = 0;
}
}
public void prevMenuItem() {

index--;
if (index <= -1) {
index = menuItemCount - 1;
}
}
public int getSelectedItemIndex() {
return index;
}
}



after:

public class ItemUseMenu extends AbstractMenu {

public ItemUseMenu() {
super(2);
}
public boolean isUseMenuSelected() {
return getSelectedItemIndex() == 0;
}
public boolean isDeployMenuSelected() {
return getSelectedItemIndex() == 1;
}
}



ここで少し重要なのは、クラスの導入された順序。つまり、OpeningMenu (ConcreteComponentB) は、ItemUseMenu (ConcreteComponentA) の後に導入された。より詳しく言えば、OpeningMenu は ItemUseMenu コピペして作られた。

ここで話はちょっとずれるけど、関連しそうな文献(まだちゃんと読んでない):

Cory Kapser and Michael W. Godfrey

'Cloning Considered Harmful' Considered Harmful

Proc. of the 2006 Working Conference on Reverse Engineering (WCRE-06)

http://plg.uwaterloo.ca/~migod/papers/


つまり、コードの進化の流れから言うとこんなかんじ:
(1)ItemUseMenu (ConcreteComponentA) 追加
(2)OpeningMenu (ConcreteComponentB) 追加
(3)AbstractMenu (AbstractComponent) 追加
(4)OpeningMenu が AbstractMenu を継承
(x)その後、納期の締切りなどの理由のためリファクタリングせず放置
(x+1)ItemUseMenu が AbstractMenu を継承
スポンサーサイト

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバックURLはこちら
http://asatohan.blog77.fc2.com/tb.php/38-baac8494
この記事にトラックバックする(FC2ブログユーザー)

FC2Ad

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