2010年7月17日土曜日

せっかくのJavaなんだから、せめて利点を生かそうよ

現在参画させてもらっている開発プロジェクトは、StrutsのWEBアプリケーション。前回、素晴らしいほどにJavaっぽくない、と言いましたが、なぜそう思ったのかというと、それはものすごく数多くあるクラス群の中で、「abstract」「Interface」で始まるクラスが一つもないから

つまり、全部がStrutsが提供する基底クラスを継承したクラスか、ただのクラスのみ。それこそ、CとかPerlとかのCGIでよくね?という感じ。考え方が、「オブジェクト指向」ではなくて「共通関数指向」とでも言うべきか。
だからなのか何なのか、ビックリするぐらい冗長で、たとえば同じ処理でもPCからのリクエストをさばくActionクラスと、モバイルからのリクエストをさばくActionクラスがほぼ同じコードで2つ、存在している。
そんな感じで、各リクエスト処理の数×2(PC&モバイル)のクラスがズラズラ!っと並んでいるわけである。
よくもまぁ、こんな長くて同じ内容のコードを大量に作って不思議に思わなかったもんだ。死ぬほどメンテしにくい、っつうかデグレ頻発すると思うんだけど…。

と愚痴っていてもしょうがないので、まさか他にそんなプロジェクトはないよね、と思いつつ、これが何かの役に立てばと思いながら「こんなときは、とりあえず抽象クラスにまとめちゃえば?」という例を。

まとめる前(悪い例)


たとえばほとんど同じような処理をしている「PcReqAction」(A)と「MobileReqAction」(B)があったとして、この冗長な2つのコードを何とかしたい場合を考えてみます。(網掛け太字の部分だけが違う)


(A)PcReqActionの内容


//Actionクラスを継承して、PcReqActionクラスを宣言
public final class PcReqAction extends Action{
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest req,
HttpServletResponse res) {

ExActionForm eaf = (ExActionForm)form;
//beanから値を取得したりする処理を記述
int val = eaf.getValue();

//データベースに接続したりしてみる
DataBaseUtil db = DataBaseUtil.getDB();

//データ検索など、ロジックを実行する
MyLogic logic = MyLogic.getInstance();
int ret = logic.execMyLogic(db,val);

//PC固有の処理
execSomething(ret);

//データベース接続を終了したり
db.close();

//遷移先を指定
return (mapping.findForward("success"));
}
}



そして同じような、

(B)MobileReqActionの内容


public final class MobileReqAction extends Action{
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest req,
HttpServletResponse res) {

ExActionForm eaf = (ExActionForm)form;
//beanから値を取得したりする処理を記述
int val = eaf.getValue();

//データベースに接続したりしてみる
DataBaseUtil db = DataBaseUtil.getDB();

//データ検索など、ロジックを実行する
MyLogic logic = MyLogic.getInstance();
int ret = logic.execMyLogic(db,val);

//モバイル固有の処理
execAnything(ret);

//データベース接続を終了したり
db.close();

//遷移先を指定
return (mapping.findForward("success"));
}
}




冗談だと思ったでしょう?
「いやいや、ここまでまったく同じようなのなんてあるの?」と思われるでしょうが、あったんです、実際。
(Actionクラスの中にアレコレ書いちゃってたりExeption拾ったりしていないのはこの際、流してください)
さてこれを、Abstractなクラス(C)を使って書いてみましょう。

Abstractでまとめた後(上記よりは良い例)


(C)AbstraceReqActionの内容

public abstract class AbstractReqAction extends Action{
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest req,
HttpServletResponse res) {

ExActionForm eaf = (ExActionForm)form;
//beanから値を取得したりする処理を記述
int val = eaf.getValue();

//データベースに接続したりしてみる
DataBaseUtil db = DataBaseUtil.getDB();

//データ検索など、ロジックを実行する
MyLogic logic = MyLogic.getInstance();
int ret = logic.execMyLogic(db,val);

//PC/モバイル固有の処理→抽象メソッド
execPeculiarFunction(ret);

//データベース接続を終了したり
db.close();

//遷移先を指定
return (mapping.findForward("success"));
}


//子クラスにやらせる処理。具体的な内容は子クラスに記載。
abstract void execPeculiarFunction(int arg){};
abstract void execPeculiarFunction(int arg);
}



書き直した(A)PcReqActionの内容

public class PcReqAction extends AbstractReqAction{
public void execPeculiarFunction(int arg){
//PC固有の処理を記述
}

}



書き直した(B)MobileReqActionの内容

public class MobileReqAction extends AbstractReqAction{
public void execPeculiarFunction(int arg){
//モバイル固有の処理を記述
}

}




いやはや、(A)も(B)も随分とスッキリしましたねぇ。そしてこれで、いわゆるDB云々やらの共通処理は1つにまとまったので、メンテもやりやすくなりました。
さらに例えば今後、スマートフォン固有の処理が必要だ!となったら、同じように抽象クラスを継承して専用クラスを作り、スマートフォンに固有の処理だけ書けばよくなります。
Javaやってる人にはくだらなすぎる内容でしたが、きっと世のプロジェクトの1%ぐらいには意外と役立つ内容なんじゃなかろうか、と期待しつつ、投稿してみます。


あと、ボク自身も決してオブジェクト指向が得意なわけではないけど、以前同僚が「コードを書いててif文が出てきたら、そこはクラス化出来る可能性がある」と言われて、それが意外と今でも役立ってたりします。

誰かの何かの参考になれば幸い。


あと、「いや、オマエもそれ、オカシイってww」というツッコミも大歓迎(というか教えてくださいお願いします)。

2 件のコメント:

ms2 さんのコメント...

内容はまさにその通り!だと思う。特にIF文のくだりもうんうん。そうだよねー。と思いました。

えらい細かいとこにつっこむと、
abstract void execPeculiarFunction(int arg){};
これ、「{}」がいらんよー。
コンパイルエラーで悩んでabstract嫌いって人が出ないように突っ込んでみた。

Takamitsu Kashiwa さんのコメント...

ms2にそう言ってもらえると安心だ。よかったよかった(ちょっとドキドキしてたw)。

ツッコミありがとう。早速修正。
詰めの甘さが露呈しましたw