late/dynamic binding と early/static binding

Javaその他デザインパターン

用語

binding (束縛)

関数の呼び出しをその関数を実際に実装しているコードに一致させるプロセスのこと。

early/static binding (事前/静的束縛)

呼び出される関数の実体がオブジェクトの型に基づいて、プログラムの実行前のコンパイル時に選択されること。したがってプログラムの実行時に束縛状態が変わることがない。

late/dynamic binding (遅延/動的束縛)

呼び出される関数の実体がオブジェクトの型に基づいて、コンパイル後のプログラム実行時に選択されること。

実装例

early/static binding

class Visitor {
    public void visit(Car c) {
        System.out.println("visit Car");
    }

    public void visit(LightCar c) {
        System.out.println("visit LightCar");
    }

    public void visit(SportsCar c) {
        System.out.println("visit SportsCar");
    }
}

class Car {}

class LightCar extends Car {}

class SportsCar extends Car {}

class TestApp {
    public void dispatch(Car c) {
        Visitor v = new Visitor();
        // この時点ではcがCarクラスかそのサブクラスであることしか分からない。
        // Vistor.visit()はオーバーロードされているが、コンパイラには引数cの型がVisitor.visit()に用意されているか分からないため、安全にvisit()を呼び出せるか不明である。
        // そのため確実に安全に呼び出せるCar型の Visitor.visit(Car c) が呼ばれるようにコンパイル時にバインディングされる。
        v.visit(c);
    }
}

public class App {
    public static void main(String[] args) throws Exception {
        LightCar lc = new LightCar();
        SportsCar sc = new SportsCar();
        TestApp testApp = new TestApp();

        testApp.dispatch(lc);
        testApp.dispatch(sc);
    }
}
visit Car
visit Car

late/dynamic binding

class Visitor {
    public void visit(Car c) {
        System.out.println("visit Car");
    }

    public void visit(LightCar c) {
        System.out.println("visit LightCar");
    }

    public void visit(SportsCar c) {
        System.out.println("visit SportsCar");
    }
}

class Car {
    public void accept(Visitor v) {
        // thisが指すのが自身(Car)であることが分かっている。
        // したがって Visitor.visit(Car c) が安全に呼び出せることが分かる。
        v.visit(this);
    }
}

class LightCar extends Car {
    public void accept(Visitor v) {
        // thisが指すのが自身(LightCar)であることが分かっている。
        // したがって Visitor.visit(LightCar c) が安全に呼び出せることが分かる。
        v.visit(this);
    }
}

class SportsCar extends Car {
    public void accept(Visitor v) {
        // thisが指すのが自身(SportsCar)であることが分かっている。
        // したがって Visitor.visit(SportsCar c) が安全に呼び出せることが分かる。
        v.visit(this);
    }
}

class TestApp {
    public void double_dispatch(Car c) {
        Visitor v = new Visitor();
        // acceptはオーバーライドされているため、安心して呼び出すことができる。
        // そのためacceptの呼び出しはdynamically bindingされる。
        c.accept(v);
    }
}

public class App {
    public static void main(String[] args) throws Exception {
        LightCar lc = new LightCar();
        SportsCar sc = new SportsCar();
        TestApp testApp = new TestApp();

        testApp.double_dispatch(lc);
        testApp.double_dispatch(sc);
    }
}
visit LightCar
visit SportsCar

補足

静的束縛のコード例のように、クラスの継承が行われている場合は、オーバーロードされた関数に対しての束縛は意図しない動作をする可能性がある。
それを回避するためのトリックが動的束縛のコード例で行っているDouble dispatchになる。オーバーロードされた関数を(コード例中のvisit関数)、各オブジェクトのオーバーライドした関数内(コード例中のaccept)で呼び出すことで動的束縛を実現している。
このDouble dispatchをうまく利用してデータ構造から処理を分離するようにしたのが、振る舞いのデザインパターンの一つであるVisitorパターンになる。

コメント

タイトルとURLをコピーしました