2008-06-16
TopHatenarはRails並の軽さで作られている
Java好きな僕としては、TopHatenarが、Javaでも(Seasar2ファミリーを使えば特に)スピーディーなWeb開発ができることの実証例になれば良いなあと思っています。
ただし、HOT deployが使えない場合はさすがに「Javaでもスピーディー」とは言えないかも*1。S2ファミリーのフレームワーク(SAStruts, Cubby, Teeda, etc)なら基本的にHOT deploy対応なので大丈夫です。
ここで、TopHatenarのコードの一部を引っ張り出して、仮に同じことをRailsでやったらどうなるか考えてみようと思います。
結論を先に言うと、すごく似た感じになります。TopHatenarはCubbyを使ったけど、SAStrutsでも大枠は同じになるはず。
以下は、http://tophatenar.com/view/{user}にアクセスしたとき呼ばれるアクションです(説明を簡単にするため書き換えた部分あり)。
- ViewAction.java
@Path("/") public class ViewAction extends Action { // リクエストパラメータ public String user; // 画面出力用オブジェクト public String diaryTitle; public int allHatenarCount; public HatenarModel model; public Rank subscriberRank; public Rank bookmarkRank; // サービス(DIされる) public HatenarService hatenarService; public RankingService rankingService; @Path("view/{user,[A-Za-z0-9\\-_]+}") public ActionResult view() { Hatenar hatenar = hatenarService.getHatenar(user); if (hatenar == null) { HatenaDiaryAPI api = new HatenaDiaryAPI(); if (api.isUserPresent(user)) { hatenarService.addHatenar(user); return new Forward("view/queued.html"); } else { return new Forward("view/error.html"); } } diaryTitle = hatenar.diaryTitle; Rankings rankings = rankingService.getRankings(); allHatenarCount = rankings.subscriberList.size(); model = hatenarService.getHatenarModel(hatenar.id); subscriberRank = rankings.subscriberList.getRank(model); bookmarkRank = rankings.bookmarkList.getRank(model); if (subscriberRank != null && bookmarkRank != null) { return new Forward("view/index.html"); } else { return new Forward("view/queued.html"); } } }
もしTopHatenarをRailsで作っていたら、上のViewActionに相当するコントローラは次のような感じになります。
- view_controller.rb(想像)
class ViewController < ApplicationController def index hatenar = get_hatenar(params[:id]) if !hatenar api = HatenaDiaryAPI.new if api.is_user_present(user) add_hatenar(user) render :action => "queued" else render :action => "error" end return end @diary_title = hatenar.diary_title rankings = get_rankings() @all_hatenar_count = rankings.subscriber_list.size @model = get_hatenar_model(hatenar.id) @subscriber_rank = rankings.subscriber_list.get_rank(model) @bookmark_rank = rankings.bookmark_list.get_rank(model) render :action => "queued" unless @subscriber_rank && @bookmark_rank end def queued end def error end end
似てますよね。Javaの場合はpublicフィールドの宣言が必要なのでちょっと行数が多いけど、IDEの補完がバシバシ効くことを考えれば、ほとんど差はないでしょう。
ちなみに、TopHatenarのプログラム総行数は約1800行、コメントと空行を除くと1300行ぐらいです(うちグラフ描画部が350行くらい)。
ここで生産性云々の議論をするつもりは全くないですけど、JavaでもLLに近い感覚で開発できるケースがあるということは十分言えると思います。
マウスイベントを親コントロールに透過させる
SwingコンポーネントはsetOpaque(false)で背景を透明にできますが、その場合でもマウスイベントはコントロール領域内で捕捉されます。
マウスイベントも透過させたい場合は、以下のようにすれば良いようです。これを応用すれば、不定形のコントロールを作ってオーバーレイさせたりできます。
public class MouseEventManipulator { public void delegateMouseEventsToParent(Component c) { c.addMouseListener(new MouseListener() { public void mouseClicked(MouseEvent e) { passToParent(e); } public void mouseEntered(MouseEvent e) { passToParent(e); } public void mouseExited(MouseEvent e) { passToParent(e); } public void mousePressed(MouseEvent e) { passToParent(e); } public void mouseReleased(MouseEvent e) { passToParent(e); } }); c.addMouseMotionListener(new MouseMotionListener() { public void mouseDragged(MouseEvent e) { passToParent(e); } public void mouseMoved(MouseEvent e) { passToParent(e); } }); } private void passToParent(MouseEvent e) { Component c = (Component) e.getSource(); Component parent = c.getParent(); Point p = SwingUtilities.convertPoint(c, e.getPoint(), parent); MouseEvent newEvent = new MouseEvent(parent, e.getID(), e.getWhen(), e.getModifiers(), p.x, p.y, e.getClickCount(), e.isPopupTrigger()); parent.dispatchEvent(newEvent); } }
*1:HOT deployにはClassCastExceptionの呪いというのがあって、最初からHOT deployを意識して作られたフレームワークじゃないと相性が悪いという点に注意が必要。