SwingでUIアーキテクチャパターン Supervising Controller編

今回はMVPパターンのうちSupervising Controllerと言われるパターンで「BMIによる肥満度判断」のサンプルを実装してみます。


前回のMVCパターンで問題となったプレゼンテーションの状態・ロジックを持つのがプレゼンターになります。また、MVCのビューはモデルの更新を受けて、ビュー自身が表示を更新を行いますが、Supervising Controllerではプレゼンターがプレゼンテーションロジックに関してビューを更新することができます。
では早速プレゼンターからコードを見ていきます。

public class Presenter{

    private boolean notifyEvent;
    private View view;
    private Model model;

    public Presenter( View view ){
        this.view = view;
    }


    public void setModel( Model model ){
        this.model = model;
    }

    public Model getModel(){
        return model;
    }

    public boolean isNotifyEvent(){
        return notifyEvent;
    }

    public void heightChanged( DocumentEvent e ){
        String text = getTextWithDocumentEvent( e );
        heightChanged( text );

    }

    public void heightChanged( String newHeight ){
        notifyEvent = true;
        try{
            model.setHeight( newHeight );
            updateColor();
        } finally{
            notifyEvent = false;
        }
    }

    public void weightChanged( DocumentEvent e ){
        String text = getTextWithDocumentEvent( e );
        weightChanged( text );

    }

    public void weightChanged( String newWeight ){
        notifyEvent = true;
        try{
            model.setWeight( newWeight );
            updateColor();
        } finally{
            notifyEvent = false;
        }
    }

    private void updateColor(){

        float bmi;
        try{
            bmi = Float.parseFloat( model.getBmi() );
        } catch ( NumberFormatException ignored ){
            return;
        }

        Color bmiColor;
        if( bmi < 18.5f ){
            bmiColor = Color.WHITE;
        } else if( bmi < 20.0f ){
            bmiColor = Color.YELLOW;
        } else if( bmi < 30.0f ){
            bmiColor = Color.ORANGE;
        } else{
            bmiColor = Color.RED;
        }

        view.setBmiBackground( bmiColor );
    }

    private String getTextWithDocumentEvent( DocumentEvent e ){
        try{
            return e.getDocument().getText( 0, e.getDocument().getLength() );
        } catch ( BadLocationException ignored ){
            return "";
        }
    }
}

前回のMVCパターンでは、モデルに実装していたBMIを表示するテキストボックスの背景色に関するロジックですが、今回のSupervising Controllerではこのプレゼンターに実装しています。
プレゼンターではユーザからのイベントをモデルに渡した後、updateColor()でBMIを表示するテキストボックスの背景色を計算してビューを直接更新しています。

public class Model{

    private EventListenerList listenerList = new EventListenerList();

    private String height = "";
    private String weight = "";
    private String bmi = "";


    public String getWeight(){
        return weight;
    }

    public String getHeight(){
        return height;
    }

    public String getBmi(){
        return bmi;
    }

    public void setWeight( String weight ){
        this.weight = weight;
        updateBMI();
        fireModelChanged();
    }


    public void setHeight( String height ){
        this.height = height;
        updateBMI();
        fireModelChanged();
    }

    private void updateBMI(){

        float height;
        float weight;

        try{
            height = Float.valueOf( this.height );
            weight = Float.valueOf( this.weight );
        } catch ( NumberFormatException e ){
            return;
        }

        if( height == 0f ){
            return;
        }

        float bmi = weight / ( height * height );

        this.bmi = Float.toString( bmi );
    }

    protected void fireModelChanged(){
        ModelListener[] listeners = listenerList.getListeners( ModelListener.class );
        ModelEvent event = new ModelEvent( this );
        for( ModelListener l : listeners ){
            l.modelChanged( event );
        }
    }

    public void addModelListener( ModelListener listener ){
        listenerList.add( ModelListener.class, listener );
    }

    public void removeModelListener( ModelListener listener ){
        listenerList.remove( ModelListener.class, listener );
    }
}

結果モデルからはプレゼンテーションに関するロジックが無くなり、純粋なドメインオブジェクトのクラスになりました。
ビューはBMIを表示するテキストボックスの背景色のセッターが追加されただけなので、ここではコードは省略します。


次回は、Passive Viewパターンです。
今回のコードはこちらです。
ソース一式(github)