M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

JavaプログラマーからみたVala(5) 通知

原典は、“Vala for Java Programmers”(2011年3月13日取得)です。

******************************

通知メカニズム

Javaの場合: リスナーを使用します。(オブザーバ・パターンの実装です。)

public interface ClickListener {
    public void clicked(Clickable source);
}
public interface Clickable {
    public void addClickListener(ClickListener l);
    public void removeClickListener(ClickListener l);
}
public class MyButton implements Clickable {
    private List<ClickListener> clickListeners;

    public MyButton() {
        this.clickListeners = new ArrayList<ClickListener>();
    }

    private void fireClickListeners() {
        for (ClickListener listener : this.clickListeners) {
            listener.clicked(this);
        }
    }

    public void addClickListener(ClickListener l) {
        if (l != null) {
            this.listeners.add(l);
        }
    }

    public void removeClickListener(ClickListener l) {
        if (l != null) {
            this.listeners.remove(l);
        }
    }

    public void test() {
        fireClickListeners();    // リスナーに着火します。
    }
}
public class Demo {

    private class MyClickListener implements ClickListener {
        public void clicked(Clickable s) {
            System.out.println("handler C");
        }
    }

    public static void main(String[] args) {
        MyButton b = new MyButton();
        b.addClickListener(new ClickListener() {
            public void clicked(Clickable s) {
                System.out.println("handler A");
            }
        });
        b.addClickListener(new ClickListener() {
            public void clicked(Clickable s) {
                System.out.println("handler B");
            }
        });
        MyClickListener handlerC = new MyClickListener();
        b.addClickListener(handlerC);
        b.test();
        b.removeClickListener(handlerC);
    }
}

Valaの場合: シグナルを使用します。(signalキーワードを用いて定義し、.connect() .disconnect()で処理を設定します。)

public class MyButton : Object {

    public signal void clicked ();

    public void test () {
        clicked ();          // シグナルを発します。
    }
}

void handler_c (MyButton source) }
    stdout.printf ("handler C\n")
}

void main () {
    var b = new MyButton ();
    b.clicked.connect ((s) => stdout.printf ("handler A\n"));
    b.clicked.connect ((s) => {
        stdout.printf ("handler B\n")
    });
    b.clicked.connect (handler_c);
    b.test ();
    b.clicked.disconnect (handler_c);
}

プロパティ変更通知

Javaの場合:

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class DemoBean {

    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    private String title;

    public String getTitle() {
        return this.title;
    }

    public void setTitle(String title) {
        String old = this.title;
        this.title = title;
        this.pcs.firePropertyChange("title", old, title);
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.removePropertyChangeListener(listener);
    }

    public static void main(String[] args) {
        DemoBean demo = new DemoBean();
        demo.addPropertyChangeListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                System.out.println("Property " + evt.getPropertyName() + " changed");
            }
        });
        demo.setTitle("hello");
        demo.setTitle("world");
    }
}

Valaの場合: Glib.Objectのサブクラスであればnotifyシグナルを持っています。

public class Demo : Object {
    public string title { get; set; }
}

void main () {
    var demo = new Demo ();
    demo.notify.connect ((s, p) => stdout.printf ("Property %s changed\n", p.name));
    demo.title = "hello";
    demo.title = "world";
}

しかし、更新前の値を取得することはできません。
もし、特定のプロパティの変更のみ考慮するのであれば、次の構文が使用できます。

demo.notify["title"].connect ((s, p) => stdout.printf ("title changed\n"));

プロパティ変更通知は、CCode属性タグにより無効化できます。このタグの直後に宣言されたプロパティは、変更通知が無効化されます。

class MyObject : Object {

    // プロパティの値が変化してもnotifyシグナルは発行されません。
    [CCode (notify = false)]
    public int without_notification { get; set; }

    // notifyシグナルは発行されます。
    public int with_notification { get; set; }
}

例外

Javaの場合: クラスベースの例外が使用されます。

public class TemperatureException extends Exception {
}
public class TooColdException extends TemperatureException {
}
public class TooHotException extends TemperatureException {
}
public class ExceptionDemo {
    private void method() throws TemperatureException {
        throw new TooColdException("It's too cold!");
    }

    public static void main(String[] args) {
        try {
            method();
        } catch (TemperatureException e) {
            System.out.println(e.getMessage());
        }
    }
}

Valaの場合: 例外はエラー(errors)と呼ばれ、クラスベースではありません。ラッピングもありません。

// Javaの例外クラスと異なり、
// エラードメイン(error domain)は、
// 複数のエラーコード(error codes)をともなう。
errordomain TemperatureError {
    TOO_HOT,
    TOO_COLD
}

void method () throws TemperatureError {
    //  new エラードメイン.エラーコード(エラーメッセージ)
    throw new TemperatureError.TOO_COLD ("It's too cold!");
}

// エラーはキャッチするか伝播させる。
// 無視をするとコンパイラが警告する。
try {
    method ();
} catch (TemperatureError e) {
    stderr.printf ("Error: %s\n", e.message);
}

コンパイラはエラーの無視を警告こそすれ、それでコンパイルプロセスを中止するということはしません。これにより固有のエラーを処理するコードを書かずにプロトタイピングを行うことが可能になります。と同時に、空の例外処理ブロックの存在が忘れ去られるという事態を抑止することが期待できます。

エラーコードはis演算子でチェックできます。

if (e is TemperatureError.TOO_COLD) {
    // ...
}

******************************

JavaプログラマーからみたVala (6)につづく──