白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

ダブルディスパッチってちょっとかっこよくない?

こんばんは。

昼間は割と暖かいですが、
朝晩は寒いので服装選びに失敗します。わたしです。

オブジェクト指向プログラミングにおいて利用されるテクニックに、
ダブルディスパッチというものがあります。

名前がかっこいい!
フィギュアスケートの技にありそう。

・・・。
えーとこれ、GoFデザインパターンのうちのひとつ、Visitorパターンと一緒に語られる事が多いのですが、
なんだかややこしいのでちょっと整理してみます。

そもそもディスパッチって何?


シングルだとかダブルだとか言う前にディスパッチがよくわかりません。
いつものようにWikipediaさんに聞いてみたいのですが、「多重ディスパッチ」という項目しかありません。
仕方がないのでこれを見ます。

多重ディスパッチ(英: Multiple dispatch)またはマルチメソッド(英: Multimethods)は、多重定義された関数やメソッドからそこで呼び出されるべき1つの定義を選出し実行する(ディスパッチする)際に、2個以上の複数の引数が関与してどれかひとつを選ぶこと(特殊化)がおこなわれるものである。

多重定義を許すプログラミング言語では、同一の名前の(すなわち、多重定義された)関数やメソッドのうちのどれを呼出す(ディスパッチする)かを決定する、ということをしなければならない。

つまり、「ディスパッチ」は「1つの定義を選出し実行する」ってところが当てはまりそうですね。
そしてさらに読んでいくと、

Java のように単一ディスパッチしかしない言語では、コードは次のようになる(ただし、Visitor パターンをこれに活用することも可能)。

とあるように、どうやらJavaは単一ディスパッチらしいです。
ほうほう。ん?

オーバーロードと多重ディスパッチの違い


「多重定義された関数やメソッドからそこで呼び出されるべき1つの定義を選出し実行する」という文章から思うのは、
Javaにはオーバーロードがあるから多重ディスパッチできるんじゃないの?ということです。

結論から書くと、

オーバーロードは、メソッド引数の定義型を基に静的に(コンパイル時に)メソッドの呼び出しを決定する
・多重ディスパッチは、引数に実際に渡される型を基に動的に(実行時に)メソッドの呼び出しを決定する

という違いがあります。

interface Animal {}
class Cat implements Animal {}
class Dog implements Animal {}

void introduction(Cat cat) {
    System.out.println("ねこだよ");
}

void introduction(Dog dog) {
    System.out.println("いぬだよ");
}

例えば上のような定義をしたとき、
変数の型をインスタンスの型とちゃんと合わせるとオーバーロードメソッドで呼び出しをちゃんと決定できます。

Cat cat = new Cat();
Dog dog = new Dog();

introduction(cat); // ねこだよ
introduction(dog); // いぬだよ

一方で、変数の型をインタフェースにしてしまうと、
インスタンスの型としてはちゃんと一致していてもメソッドを呼び出すことはできません。
オーバーロードメソッドの選択時にインスタンスの型は参照されないためです。
多重ディスパッチが可能な言語では、この呼び出しは特に問題ありません。

Animal animal1 = new Cat();
Animal animal2 = new Dog();

//method(animal1); エラー
//method(animal2); エラー

単一ディスパッチの言語でもなんとかするために、
メソッド引数の型をAnimalにしてみましょう。

void introduction(Animal animal) {
    if (animal instanceof Cat) {
        System.out.println("ねこだよ");
    } else if (animal instanceof Dog) {
        System.out.println("いぬだよ");
    } else {
        System.out.println("?");
    }
}

Animal animal1 = new Cat();
Animal animal2 = new Dog();

introduction(animal1); // ねこだよ
introduction(animal2); // いぬだよ

おぉ、カッコ悪い!
オブジェクト指向の言語であれば普通はそれぞれのクラス側に処理を書く方が綺麗ですね。

interface Animal {
    void introduction();
}

class Cat implements Animal {
    @Override
    public void introduction() {
        System.out.println("ねこだよ");
    }
}

class Dog implements Animal {
    @Override
    public void introduction() {
        System.out.println("いぬだよ");
    }
}

Animal animal1 = new Cat();
Animal animal2 = new Dog();

animal1.introduction(); // ねこだよ
animal1.introduction(); // いぬだよ

なんかもう別に多重ディスパッチとかなくていいような気がしてきました。

プレゼントはわたし


さて、最初の定義を2引数に変えてみました。

interface Animal {}
class Cat implements Animal {}
class Dog implements Animal {}

interface Food {}
class CatFood implements Food {}
class DogFood implements Food {}

void feed(Cat cat, CatFood catFood) {
    System.out.println("うまいにゃ");
}

void feed(Cat cat, DogFood dogFood) {
    System.out.println("まぁまぁにゃ");
}

void feed(Dog dog, CatFood catFood) {
    System.out.println("まぁまぁわん");
}

void feed(Dog dog, DogFood dogFood) {
    System.out.println("うまいわん");
}

Animal animal1 = new Cat();
Animal animal2 = new Dog();

Food food1 = new CatFood();
Food food2 = new DogFood();

//feed(animal1, food1); エラー
//feed(animal1, food2); エラー
//feed(animal2, food1); エラー
//feed(animal2, food2); エラー

やっぱりインタフェースが使えなくて困るので、ダサいバージョンにしてみます。

void feed(Animal animal, Food food) {
    if (animal instanceof Cat && food instanceof CatFood) {
        System.out.println("うまいにゃ");
    } else if (animal instanceof Cat && food instanceof DogFood) {
        System.out.println("まぁまぁにゃ");
    } else if (animal instanceof Dog && food instanceof CatFood) {
        System.out.println("まぁまぁわん");
    } else if (animal instanceof Dog && food instanceof DogFood) {
        System.out.println("うまいわん");
    } else {
        System.out.println("?");
    }
}

Animal animal1 = new Cat();
Animal animal2 = new Dog();

Food food1 = new CatFood();
Food food2 = new DogFood();

animal1.eat(food1); // うまいにゃ
animal1.eat(food2); // まぁまぁにゃ
animal2.eat(food1); // まぁまぁわん
animal2.eat(food2); // うまいわん

読みづらいけど、とりあえず呼べるようになりました。
では、先程と同じようにクラス側に処理を移しましょう。

interface Animal {
    void eat(Food food);
}
class Cat implements Animal {
    @Override
    public void eat(Food food) {
        if (food instanceof CatFood) {
            System.out.println("うまいにゃ");
        } else if (food instanceof DogFood) {
            System.out.println("まぁまぁにゃ");
        } else {
            System.out.println("?");
        }
    }
}
class Dog implements Animal {
    @Override
    public void eat(Food food) {
        if (food instanceof CatFood) {
            System.out.println("まぁまぁわん");
        } else if (food instanceof DogFood) {
            System.out.println("うまいわん");
        } else {
            System.out.println("?");
        }
    }
}

困ったことにダサさが残っています。
仕方がないのでメソッドを分けましょう。

interface Animal {
    void eat(CatFood food);
    void eat(DogFood food);
}
class Cat implements Animal {
    @Override
    public void eat(CatFood food) {
        System.out.println("うまいにゃ");
    }
    @Override
    public void eat(DogFood food) {
        System.out.println("まぁまぁにゃ");
    }
}
class Dog implements Animal {
    @Override
    public void eat(CatFood food) {
        System.out.println("まぁまぁわん");
    }
    @Override
    public void eat(DogFood food) {
        System.out.println("うまいわん");
    }
}

Animal animal1 = new Cat();
Animal animal2 = new Dog();

Food food1 = new CatFood();
Food food2 = new DogFood();

//animal1.eat(food1); エラー
//animal1.eat(food2); エラー
//animal2.eat(food1); エラー
//animal2.eat(food2); エラー

また呼び出せなくなってしまいました。
そこで、さらにこんなことをしてみます。

interface Food {
    void eatenBy(Animal animal);
}
class CatFood implements Food {
    @Override
    public void eatenBy(Animal animal) {
        animal.eat(this);
    }
}
class DogFood implements Food {
    @Override
    public void eatenBy(Animal animal) {
        animal.eat(this);
    }
}

Animal animal1 = new Cat();
Animal animal2 = new Dog();

Food food1 = new CatFood();
Food food2 = new DogFood();

food1.eatenBy(animal1); // うまいにゃ
food2.eatenBy(animal1); // まぁまぁにゃ
food1.eatenBy(animal2); // まぁまぁわん
food2.eatenBy(animal2); // うまいわん

できた!(なんかひっくり返ったけど)

ポイントはFoodインタフェースの実装クラスで、
引数として渡されたオブジェクトに対して自分自身を引数として呼び出し返していることです。

このようにして1段階でメソッドの呼び出し(ディスパッチ)が確定できない場合に、
2つのオブジェクトを使って2段階に呼び出すことを「ダブルディスパッチ」といいます。

Visitorパターン


さてさて、やっとこさダブルディスパッチの正体がわかりましたが、
最初にも触れたように、これを利用したデザインパターンにVisitorパターンがあるので併せて確認しておきます。

Visitorパターンの目的は「アルゴリズムをオブジェクトの構造から分離する」ことにあります。

何らかのデータはAcceptor(受け入れ側)になります。

interface Acceptor {
    void accept(Visitor visitor);
}

class Cat implements Acceptor {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

class Dog implements Acceptor {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

class Fish implements Acceptor {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

データに対する処理はVisitor(訪問者側)になります。

interface Visitor {
    void visit(Cat cat);
    void visit(Dog dog);
    void visit(Fish fish);
}

class MofMofVisitor implements Visitor {

    @Override
    public void visit(Cat cat) {
        System.out.println("もふもふじゃー!あー、やめて!引っ掻かないで!");
    }

    @Override
    public void visit(Dog dog) {
        System.out.println("もふもふじゃー!もふもふもふー");
    }

    @Override
    public void visit(Fish fish) {
        System.out.println("もふもふじゃー!びしょびしょ・・・");
    }
}

処理がちゃんとデータ構造の外側にあるため、
新しいVisitorを作れば別の処理ができることがわかりますね。

List<Acceptor> list = new ArrayList<>();
list.add(new Cat());
list.add(new Dog());
list.add(new Dog());
list.add(new Fish());
list.add(new Cat());
list.add(new Fish());

Visitor visitor = new MofMofVisitor();

list.stream().forEach(acceptor -> acceptor.accept(visitor));

また、Visitorは状態を持つことができるので、
上の例であれば、猫、犬、魚がそれぞれ何匹づつかと最終的に集計することなどもできます。

Compositeパターンと組み合わせると、ディレクトリ構造を渡り歩きながら処理もできますね。

必殺技じゃなかった


わたしはたまにチェッカークラスで使ったりするくらいです。
チェックする対象がどんな構造になっていてもチェック処理を一か所にまとめられるし、
結果を貯めておけるのもポイントが高いです。

難しいものはかっこよく見えるものですが、
基本的にはプログラムに求められるのはシンプルさですから使いどころは難しいですよね。

何にしろ、引き出しが増えるのは良いものです。

Java使いがC#を勉強する その⑦ アクセス修飾子

こんばんは。

本棚がいっぱいになって困っています。わたしです。
今回はアクセス修飾子について見ていきます。

基本


アクセス修飾子は型および型のメンバーにつけることができます。
アクセス修飾子の種類は以下の通りです。
  

修飾子 Java C#
public どこからでもアクセス可能 どこからでもアクセス可能
protected 同一パッケージまたは派生クラスから
アクセス可能
派生クラスからアクセス可能
protected internal - 現在のアセンブリまたは派生クラスから
アクセス可能
internal - 現在のアセンブリからアクセス可能
なし 同一パッケージからアクセス可能(default) クラスはinternal、メンバーはprivateと同じ扱い
private 自クラスからのみアクセス可能 自クラスからのみアクセス可能

※現在のアセンブリコンパイルされたコードライブラリのこと(具体的にはEXEとDLL)
※privateはインタフェースとか列挙型ももちろん可

 
挙動が同じなのはpublicとprivateのみですね。

Javaの場合には「何もつけない」修飾子(defaul修飾子)が存在しているのに対して、
C#の場合には何もついていないときはinternalまたはprivate扱いということになっているので注意。

また、Javaがパッケージ単位でアクセス制限をしているのに対して、
C#アセンブリ単位でアクセス制限をしているのもポイントです。

 

クラスのアクセス修飾子


アクセス修飾子を付ける対象によって、選択肢が異なることには注意が必要です。

ネストしていない(一番外側の)クラス

修飾子 Java C#
public どこからでもアクセス可能 どこからでもアクセス可能
internal - 現在のアセンブリからアクセス可能
なし 同一パッケージからアクセス可能(default) internalと同じ扱い

Javaでは一番外側のpublicなクラスは一つの.javaファイルに一つしか定義できません。
また、ファイル名とpublicなクラスの名前は一致している必要があります。(インタフェースも同じ)

クラスのメンバー(変数とかメソッドとか)

修飾子 Java C#
public どこからでもアクセス可能 どこからでもアクセス可能
protected 同一パッケージまたは派生クラスから
アクセス可能
派生クラスからアクセス可能
protected internal - 現在のアセンブリまたは派生クラスから
アクセス可能
internal - 現在のアセンブリからアクセス可能
なし 同一パッケージからアクセス可能(default) privateと同じ扱い
private 属するクラスまたは属するクラスの
ネストクラスからのみアクセス可能
属するクラスまたは属するクラスの
ネストクラスからのみアクセス可能

注意としてはネストクラスで外側のクラスの方が可視性が低い場合、外側のクラスの可視性に引っ張られます。
例えば、外側のクラスが修飾子なしでネストクラスがpublicだとすると、
外側のクラスが見えない場合にはネストクラスも見えません。当然といえば当然。

ネストしているクラス、インタフェース

メンバーと同じです。

インタフェースのアクセス修飾子

 

ネストしていない(一番外側の)インタフェース

修飾子 Java C#
public どこからでもアクセス可能 どこからでもアクセス可能
internal - 現在のアセンブリからアクセス可能
なし 同一パッケージからアクセス可能(default) internalと同じ扱い

 

インタフェースのメンバー(メソッドとか)

修飾子 Java C#
public どこからでもアクセス可能 書けない(public扱い)
なし publicと同じ扱い publicと同じ扱い

どちらもpublicしかないのですが、Javaでは書いても書かなくても同じなのに対して、
C#では書くとコンパイルエラーになってしまいます。
ちなみにアクセス修飾子ではないですが、abstractも同じ扱いです。

また、Javaでは定数(public static finalな変数)を定義できますが、C#では定義できません。
定数クラスをインタフェースで作る派の方はご注意。

 

ネストしているクラス、インタフェース

Javaの場合、メンバーと同じです。
C#ではインタフェースには型を宣言できません。
個人的にはネストインタフェースとか結構好きなんですけど、ダメなんですね。

継承とかなんとか


クラスの継承の際に可視性を変更することができます。
Javaでは上げることも下げることもできます。

class Test {

    class A {}
    
    public class C extends A {}
    
    private class D extends A {}
}

C#では上げることはできません。

class Test 
{

    class A {}

    // ダメ
    //public class C : A {}

    private class D : A {}
}


また、Javaではメソッドをオーバーライドする際に可視性を変更することもできます。
上げることはできますが、下げることはできません。

class Test {

    abstract class A {
        protected abstract void method();
    }

    // OK
    class B extends A {
        @Override
        public void method() {}
    }

    // ダメ
//    class C extends A {
//        @Override
//        private void method() {}
//    }
}

C#では可視性の変更はできません。

class Test 
{

    abstract class A 
    {
        protected abstract void method();
    }

    // ダメ
    //class B : A 
    //{
    //    public override void method() { }
    //}

    // ダメ
    //class C : A
    //{
    //    private override void method() { }
    //}
}


少し余計な話をすると、Javaの場合は外側のクラスを継承して可視性を上げるとメンバを外から見ることができます。
ので、修飾子なしクラスにpublicなネストクラスを作ることに全く意味がないわけでもないことも・・・ない?

class A {
    public class B {
    }
}
public class C extends A {
}

とした上で、

// A.B c = new A().new B(); Aが見つからないのでダメ
C.B c = new C().new B(); // OK

みたいに参照できるわけですね。
まぁそんなことしないけど。

 

なんでもpublicにすればいいってもんじゃないんだぞ


ちゃんと考えると結構難しいアクセス修飾子ですが、
publicかprivateしかないプロジェクトなんて言うのも結構あったりしますよね。

かつて私のいたプロジェクトもprotectedやdefaultを使っているのが自分だけだったりしました。
フィールドは全部private、クラスは全部publicなんていうのはちょっとどうかと思います。

Java使いがC#を勉強する その⑥ 拡張メソッド

こんばんは。

何やら今日は妙に温かかったですね。
そして明日は寒いらしいですね。体調に気をつけましょう。

さて、C#には拡張メソッドというデンジャラスな機能があります。
今回はこれを見ていきます。

拡張ってどういうことだ


通常、あるクラスにメソッドを追加したい場合には、
そのクラスに直接メソッドを書きます。

public class Hoge {}

public class Hoge
{
  public void Piyo()
  {
    Console.WriteLine("ぴよ");
  }
}

しかし、そのクラスが自分の持ち物ではない場合、
むやみやたらにクラスに変更を加えるわけにはいきません。

そのため、対象のクラスを継承した派生クラスを作ります。
そうすると派生クラスでは親クラスのメソッドと自クラスのメソッドの両方が使えます。

public class HogeEx : Hoge
{
  public void Piyo()
  {
    Console.WriteLine("ぴよ");
  }
}

が、元のクラスに変更を加えたくない(再コンパイルしたくない)けれど、
メソッドを追加したいという場合に拡張メソッドという機能を使うことができます。

public class ExtensionMethods
{
  public void Piyo(this Hoge hoge)
  {
     Console.WriteLine("ぴよ");

  }
}

つまり、クラスにメソッドを外から「拡張」するわけですね。

 

使うべきじゃない?

 

なかなかに便利そうな拡張メソッドですが、いいことばかりではありません。

①Objectクラスにメソッドを拡張した場合、すべての派生クラスにメソッドが追加される
②拡張メソッドの定義はどこで行ってもよいため、メソッドの定義が分散する
③クラスのメソッドとシグネチャが一致した場合、拡張メソッドは優先されない
④拡張メソッドの定義が複数読み込まれた場合、どちらも利用できない

などなど、様々な問題があります。

③では言語仕様の変更やライブラリのアップデートに伴ってメソッドが追加され、
既存のコードの挙動が変わる可能性があります。

④でも同じ拡張メソッドが追加された場合、コードが正常に動作しません。


一般的に有用に利用されるのは以下の2つの場面が多いようです。

インタフェースへのメソッド追加

前回の記事でも触れましたが、基本的にインタフェースにメソッドの実装を書くことはできません。
そのため、多重継承のないC#ではMix-Inができないわけです。

しかし、拡張メソッドを使うとインタフェースにもメソッドを追加することができます。
前回の記事のコードそのままです。

public interface IPiyo {}

public static class Util
{
  static string Piyo(this IPiyo piyo) {
    return "ぴよ";
  }
}

便利ですけど、そのインタフェースに書きたくないですか?
はて。

enumへのメソッド追加

C#では列挙型にメソッドを実装することができません。
それどころか値を持たせることすらできません。

enum Direction { North, East, South, West }

static class DirectionExtension {

    static string Name(this Direction direction) {
        string name = "";
        switch (direction) {
            case Direction.North:
                name = "北";
                break;
            case Direction.East:
                name = "東";
                break;
            case Direction.South:
                name = "南";
                break;
            case Direction.West:
                name = "西";
                break;
        }
        return name;
    }
}

一応、拡張メソッドを使うとメソッドが作れます。
値は渡せませんが。
うわー死んでしまう。

なんかこれなら自分でタイプセーフEnum作りたい。

sealed class Direction {

    public string Name { get; private set; }

    private Direction(string name) {
        this.Name = name;
    }

    public static readonly Direction North = new Direction("北");
    public static readonly Direction East = new Direction("東");
    public static readonly Direction South = new Direction("南");
    public static readonly Direction West = new Direction("西");
}


ご利用は計画的に


個人で作るアプリでは標準ライブラリのクラスなどに
好き勝手に拡張メソッドを作ると楽しいのかもしれません。

が、チームで開発するときにはあまりお勧めできなそうです。
用法用量を守って正しく使いましょう。