NullObject模式

知道这个模式还是通过《重构》,这个模式的出现还是了为了解决代码重复的坏味道。在项目中很经常见到类似下面这样的代码:

if (prj.getProjectId == null )
    plan.setCost(
0.0 );
else
    plan.setCost(prj.getCost());


   我们在很多地方有类似的检查对象是否为null,如果为null,需要一个默认值等等这样的场景。显然,代码重复是坏味道,怎么消除这个坏味道呢?答案就是使用NullObject替代之,Null Object继承原对象。

class  NullProject  extends  Project{
   
public   boolean  isNull(){
      
return   true ;
   }
}
class  Project{
   
private   double  cost;
   
private  String projectId;
   .
   
public   boolean  isNull(){
        
return   false ;
   }
}


那么,原来的代码可以改写为:

if (prj.isNull())
    plan.setCost(
0.0 );
else
    plan.setCost(prj.getCost());


    如果Null Object的引入仅仅是带来这个好处,似乎没有理由让我们多敲这么多键盘。问题的关键是类似上面这样的判断也许出现在很多处,那么有价值的技巧出现了,我们在NullObject覆写getCost,提供缺省值:

class  NullProject  extends  Project{
   
public   boolean  isNull(){
      
return   true ;
   }
   
public   double  getCost(){
      
return   0.0 ;      
   }
}

    因此,检查对象是否为null的代码可以去掉if...else了:

plan.setCost(prj.getCost());


    请注意,只有那些大多数客户端代码都要求null object做出相同响应时,这样的行为才有意义。比如我们这里当工程id为null,很多地方要求费用就默认为0.0。特殊的行为我们仍然使用isNull进行判断。
    当然,另外在需要返回NullObject的地方,你应该创建一个null object以替代一般的对象,我们可以建立一个工厂方法:

class  Project{
   
private   double  cost;
   
private  String projectId;
   .
   
public   boolean  isNull(){
        
return   false ;
   }
   
public  Project createNullProject(){
        
return   new  NullProject();
   }
}


   Null Object模式带来的好处:减少了检查对象是否为null的代码重复,提高了代码的可读性,通常这些Null Object也可以为单元测试带来简便。
以上文章引用自:http://www.java125.cn/article.asp?id=1157
下面是一篇日文介绍NullObject模式的文章,比较好。
文章引自http://www.pc-view.net/Solution/040120/page133.html
NullObjectパターン

 次に、ImageComponentクラスのpaintComponentメソッドについて考えてみよう。フィールドimgsとcurrentの値によって描画処理が切り替わるようにコーディングされているが、この切り替えは描画時に毎回行う必要はない。マウスクリックによって描画する内容が変わったときだけ、切り替えが発生すれば良いのだが、この方法ではpaintComponentメソッドが呼び出されるたびに条件分岐処理が発生している。特に起動時の描画にあたってはnullチェックをしているが、これを改善するにはNullObjectパターンが使える。

 NullObjectパターンは、ある処理をするにあたり、呼び出し側でnullチェックをしなくてもいいように「何も処理をしないクラスのインスタンス」を用意するというパターンである。NullObjectパターンのクラス関連図は以下のようになる(図7)。


 Delegatorクラスは、AbstractOperationへ操作を委譲し、nullチェックをせずに委譲した操作を実行する。AbstractOperationインタフェースは、Delegatorクラスより委譲された操作の宣言をする。RealOperationクラスはAbstractOperationインタフェースを実装し、実際に実行される何らかの操作を実装する。

 NullOperationクラスもAbstractOperationインタフェースを実装するが、何も実行しないように各操作を実装する。なお、NullObjectパターンではNullOperationのインスタンスが1つあれば十分な場合が多いので、Singletonパターンを併用される場合も多い。

 NullObjectパターンの特長を整理すると次のようになる。

  1. NullObjectパターンを適用したクラスのインスタンスについては、nullチェックをする必要がなくなるため、コードが単純になる。
  2. NullObjectパターンでは、何もしないクラスを作成するため、プログラム内のクラスが増える。コードの単純化とクラス数の増加についてのトレードオフを考慮する必要がある。

 このNullObjectパターンの特長を理解した上で、スライドショーアプリケーションへ適用してみよう。Immutableパターンの適用も含めるとクラス関連図は次のようになる(図8)。ただし、パッケージ名は省略している。


 ImageComponentクラス(Delegatorクラスに相当)のpaintComponentメソッド内から条件分岐処理をなくすために、描画処理用インタフェースSlidePainter(AbstractOperationインタフェースに相当)を用意し、このインタフェースを実装するクラスが描画を実行するようにする(リスト10)。このインタフェースでは描画用メソッドpaintComponent(Graphics g, int w, int h)だけを宣言する。こうすることにより、ImageComponentクラスのpaintComponentメソッドでは条件分岐処理をする代わりに「painter[current].paintComponent(g, w, h);」とすれば良いだけになる。

 ここで、フィールドpainterはSlidePainterの配列であり、java.awt.Imageの配列imgsと置き換わっている。コンストラクタでは、painterはサイズが1の配列として用意し、NullSlidePainterクラス(NullOperationクラスに相当)のインスタンスをpainter[0]へ代入している。これにより、起動時の画面ではNullSlidePainterクラスのインスタンスによる描画が行われる。後述するが、このクラスのpaintComponent(Graphics g, int w, int h)メソッドでは実際には何の描画もしない。

 startメソッドが呼ばれたときには、配列painterのインスタンスを作成し直した上で、painter[0]へは開始用画面を描画するStartSlidePainterクラス(RealOperationクラスに相当)のインスタンスを、painter[1]へはimg1.jpgを描画するImageSlidePainterクラス(RealOperationクラスに相当)のインスタンスを、painter[2]へはimg2.jpgを描画するImageSlidePainterクラスのインスタンスを代入する。

 なお、これらのインスタンスがSlidePainter型配列のpainterへ要素として代入できていることからもわかるが、StartSlidePainterクラスもImageSlidePainterクラスもSlidePainterインタフェースを実装している。

リスト10:デザインパターン版ImageComponent.java(抜粋)

public class ImageComponent extends JComponent {
(略)
  private SlidePainter[] painter;
(略)
  public ImageComponent(Dimension size, String prefix, 
      String suffix, int num) {
(略)
    painter = new SlidePainter[1];
    painter[0] = new NullSlidePainter();
(略)
  }
  public void start() {
    if (painter.length > 1) return; //すでに開始している
    painter = new SlidePainter[num + 1];
    painter[0] = new StartSlidePainter();
    for (int i=1 ; i<painter.length ; i++) {
      painter[i] = new ImageSlidePainter(this, prefix, suffix, i);
    }
(略)
  protected void paintComponent(Graphics g) {
    int w = getWidth() - 1;
    int h = getHeight() - 1;
    painter[current].paintComponent(g, w, h);
    g.setColor(Color.BLUE);
    g.drawRect(0, 0, w, h);
  }
}

 ご覧の通り、ImageComponentクラスのpaintComponentメソッド内からif文による条件分岐が消えている点に注目して欲しい。また、初期版のImageComponentクラスのpaintComponentメソッド内で起動時の描画を処理していたコードは、「if (imgs == null) { }」 という部分であったが、この部分を担うクラスがNullSlidePainterクラス(リスト11)であり、次のようにpaintComponent(Graphics g, int w, int h)メソッドでは何もしていないという点がポイントである。このように「何も処理をしないメソッドを実装するクラス」というのを用意することにより、ImageComponentクラスのpaintComponentメソッドがコンパクトに記述できるのである。

リスト11:デザインパターン版NullSlidePainter.java

public class NullSlidePainter implements SlidePainter {
  public void paintComponent(Graphics g, int w, int h) { }
}

 スライドショー開始時の描画を処理していたコードは「g.setColor(Color.BLACK); g.fillRect(0, 0, w, h);」であるが、こちらはStartSlidePainterクラスが担っている(リスト12)。

リスト12:デザインパターン版StartSlidePainter.java

public class StartSlidePainter implements SlidePainter {
  public void paintComponent(Graphics g, int w, int h) {
    g.setColor(Color.BLACK);
    g.fillRect(0, 0, w, h);
  }
}

 写真イメージファイルの描画を処理していたコードは「g.drawImage(imgs[current], 0, 0, w, h, this);」であるが、NullSlidePainter クラス、StartSlidePainter クラスと同様にして、ImageSlidePainterクラスのpaintComponent(Graphics g, int w, int h)メソッド内で呼び出されるようになっている(リスト13)。

リスト13:デザインパターン版ImageSlidePainter.java

public class ImageSlidePainter implements SlidePainter {
(略)
  public void paintComponent(Graphics g, int w, int h) {
    g.drawImage(img, 0, 0, w, h, component);
  }
}

 ImmutableパターンとNullObjectパターンを適用したサンプルクラスをコンパイルして実行すると、初期版のスライドショーアプリケーションと同じように動作する事がわかるはずである。どちらも、非常に単純なパターンであるので、応用できる場面も非常に多い。活用できる機会が多いので、ぜひ覚えておこう。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值