Java:ラッピングのすゝめ~なぜラップしないのか?

2018年9月20日

あちこちのコードを見ると、既存のライブラリの使用時にラッピングのされてないものが多数見受けられる。これを長年やってきた私としては、「なぜラッピングしないのだろう?その方が全然便利になるのに」と強く感じるところである。

ラップ前

例えば以下のSwingプログラムを考えてみる。単純にYES/NOのボタンを表示し、YES/NOを出力するだけだ。

import java.awt.*;

import javax.swing.*;

public class Sample {

  public static void main(String[]args) {    
    JFrame frame = new JFrame("Sample");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setBounds(50, 50, 400, 400);
    frame.setLayout(new FlowLayout());

    JButton yesButton = new JButton("YES");
    yesButton.addActionListener(e->System.out.println("yes"));
    frame.add(yesButton);
    JButton noButton = new JButton("NO");
    noButton.addActionListener(e->System.out.println("no"));
    frame.add(noButton);
    frame.setVisible(true);
  }
}

ちなみに、これは当然Java8での記述である。それ以前は、ボタン部分の定義は以下のように書かねばならなかった。Java8でかなり楽になったと思う。

    JButton yesButton = new JButton("YES");
    yesButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        System.out.println("yes"); 
      }      
    });
    JButton noButton = new JButton("NO");
    noButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        System.out.println("no"); 
      }      
    });

問題点とその解決提案

上記のプログラム例の問題点は明らかだと思うが、フレームを作り、ボタン二つ入れるだけにも関わらず、これだけのコード記述が必要になる。さらにボタンやらパネルやらを放り込めば、大量のコードになることは明らかだ。

しかも、コンポーネントを中間的に保持するだけの目的のために、いくつもの変数が必要になり、その管理も面倒だ。

例えばボタンの生成とリスナーの設定だが、必ず変数が必要になってしまうのである。

    JButton yesButton = new JButton("YES");
    yesButton.addActionListener(e->System.out.println("yes"));
    frame.add(yesButton);

もし、addActionListenerの戻り値がJButtonであれば、以下のように書けて、変数等必要なかったのだが。。。

frame.add(new JButton("YES").addActionListener(e->System.out.println("yes")));

ラップする理由としては、「ライブラリAPIの呼び出し方を好みに合わせて変えてしまう」ということだ。なぜこれがきちんと議論されないのかが私には全くわからないのである。

具体的なラップの仕方1

好みに合わせてラップ方法はいくらでもある。例えば、いまボタンだけラップしてしまうことを考えてみる。例えば以下のコードになる。

import java.awt.*;
import java.util.function.*;

import javax.swing.*;

public class Sample {

  public static abstract class SgContainer {
    public abstract Container w();
  }

  public static class SgButton extends SgContainer {
    private JButton button;    
    public SgButton(String label, Consumer<SgButton>handler) {
      button = new JButton(label);
      button.addActionListener(e->handler.accept(this));
    }
    public JButton w() {
      return button;
    }
  }

  public static void main(String[]args) {    
    JFrame frame = new JFrame("Sample");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setBounds(50, 50, 400, 400);
    frame.setLayout(new FlowLayout());

    frame.add(new SgButton("YES", b->System.out.println("yes")).w());
    frame.add(new SgButton("NO", b->System.out.println("no")).w());
    frame.setVisible(true);
  }
}

当然ボタンラッパーのコードは増えるが、これは他でも使えるわけであり、たくさんのボタンを作成してそのハンドラーを設定することを考えれば、非常に簡単になることがわかるはずだ。

具体的なラップの仕方2

さらにはJFrameもラップしてしまい、前述のSgButtonを一度に複数addできるようにしてみる。

import java.awt.*;
import java.util.function.*;

import javax.swing.*;

public class Sample {

  public static abstract class SgContainer {
    public abstract Container w();
  }

  public static class SgButton extends SgContainer {
    private JButton button;    
    public SgButton(String label, Consumer<SgButton>handler) {
      button = new JButton(label);
      button.addActionListener(e->handler.accept(this));
    }
    public JButton w() {
      return button;
    }
  }

  public static class SgFrame extends SgContainer {
    private JFrame frame;
    public SgFrame(String title) {      
      frame = new JFrame(title);
    }
    public SgFrame setExitOnClose() {
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      return this;
    }
    public SgFrame setBounds(int x, int y, int w, int h) {
      frame.setBounds(x, y, w, h);
      return this;
    }
    public SgFrame setLayout(LayoutManager layout) {
      frame.setLayout(layout);
      return this;
    }
    public SgFrame add(SgContainer...containers) {
      for (SgContainer c: containers) {
        frame.add(c.w());
      }
      return this;
    }
    public JFrame w() {
      return frame;
    }
  }

  public static void main(String[]args) {    
    SgFrame frame = new SgFrame("Sample")
      .setExitOnClose()
      .setBounds(50,50,400,400)
      .setLayout(new FlowLayout())
      .add(
        new SgButton("YES", b->System.out.println("yes")),
        new SgButton("NO", b->System.out.println("no"))
      );
    frame.w().setVisible(true);
  }
}

setVisibleを残してあるが、滅多に使わない機能はw()を介して元のオブジェクトにアクセスすれば何とでもなることを示している。