C#クラスの委譲とインターフェイス

C# コンピュータ
C#
グラフィックビューワを作っていて、画像の表示を「単ページ」や「見開き表示」、「半分表示」などいくつかの表示モードの切り替え機能を設けたいと思います。方法として表示モードのフラグに合わせてコードを分岐するようにプログラミングする方法が思いつきますが、複雑になりすぎてたびたび挫折しています。
どのモードにしても機能的には、表示する画像を返す機能とか、表示する画像を次の又は前の画像移動するなど、かなり共通する部分が多くコード的に重複する部分が多数発生します。重複する部分をコンパクトにまとめようとすると、コードの見通しが悪くなり、後から見ると作成者本人が見ても何をやっているか判読不能なソースファイルが出来上がります。
こういった問題を解決するのがオブジェクト指向プログラミングはずなので、いくつか試行錯誤してみます。
スポンサーリンク

継承

サンプルーコード&実行結果
using System;

// 継承

class BaseView
{
    public string Value = "";
    public virtual void Say() { Console.WriteLine("Say:{0}", Value); }
}
class SingleView : BaseView
{
    public override void Say() { base.Say(); }
}
class DualView : BaseView
{
    public override void Say() { base.Say(); }
}
class HalfView : BaseView
{
    public override void Say() { Console.WriteLine("HalfView.Say:{0}", Value); }
}
class Program
{
    static void Main()
    {
        BaseView[] viewMode = new BaseView[]
        {
            new SingleView { Value = "Single", },
            new DualView { Value = "Dual", },
            new HalfView { Value = "Half", },
        };

        viewMode[0].Say();
        viewMode[1].Say();
        viewMode[2].Say();
    }
}
PS>.\Inheritance   
Say:Single
Say:Dual
HalfView.Say:Half
BaseViewクラスに共通するメソッドやプロパティを持たせ、派生したクラスで各表示モードごとの機能を持たせる設計です。
各クラスのオブジェクトをBaseView配列の要素とし添え字を変更することで表示モードの切り替えを表現しています。
 BaseViewのメソッドやプロパティを派生クラスで使いまわすことが出来ますし、派生クラスでオーバーライドすることで派生クラス独自の機能を持たせることも出来ます。
一見これで良さそうな気もしましたが問題があります。
表示モードの切り替えをするということは、同じ画像をモードを変えることで見せ方を変えるわけです。
今回の設計だと、表示モードの各オブジェクトが独立しているので、モードごとに異なる画像を表示させることが出来てしまいます。
出来てうれしい場面もありますが、今回のケースは各オブジェクトで同じ画像をさし示すように維持するのは大変です。

委譲

今回の場合クラスの派生を作るより、一つのオブジェクトをラップするようなクラスで編集モードを表現する方法がよさそうです。
BaseViewクラスに基本となる表示機能を持たせる点は継承とおなじですが、BaseViewのインスタンスを表示モードごとのクラスのメンバーとして保持します。
表示モードごとにメソッドやプロパティを用意してあげる必要がありますが、実装ではBaseViewのインスタンスのメソッドやプロパティを利用することが出来ます。BaseViewクラスの継承では無いので必要のないメソッドは実装しないことも出来ますし、継承におけるオーバーライドのようなことも出来ます。
サンプルーコード&実行結果
using System;

// 委譲

class BaseView
{
    public string Value = "";
    public void Say() { Console.WriteLine("Say:{0}", Value); }
}
class SingleView
{
    BaseView _BaseView;
    public SingleView(BaseView baseView) { _BaseView = baseView; }
    public string Value
    {
        set { _BaseView.Value = value; }
        get { return _BaseView.Value; }
    }
    public void Say() { _BaseView.Say(); }
}
class DualView
{
    BaseView _BaseView;
    public DualView(BaseView baseView) { _BaseView = baseView; }
    public string Value
    {
        set { _BaseView.Value = value; }
        get { return _BaseView.Value; }
    }
    public void Say() { _BaseView.Say(); }
}
class HalfView
{
    BaseView _BaseView;
    public HalfView(BaseView baseView) { _BaseView = baseView; }
    public string Value
    {
        set { _BaseView.Value = value; }
        get { return _BaseView.Value; }
    }
    public void Say() { Console.WriteLine("HalfView.Say:{0}", _BaseView.Value); }
}
class Program
{
    static void Main()
    {
        /*
        無理
        BaseView[] viewMode = new BaseView[]
        {
            new SingleView { Value = "Single", },
            new DualView { Value = "Dual", },
            new HalfView { Value = "Half", },
        };
        */
        BaseView bv = new BaseView{ Value = "Baaaaase", };

        var single = new SingleView(bv);
        var dual = new DualView(bv);
        var half = new HalfView(bv);

        single.Say();
        dual.Say();
        half.Say();
    }
}
PS>.\Transfer   
Say:Baase
Say:Baase
HalfView.Say:Baase
継承より委譲の方が自由度が高そうな気がしますが、委譲はプログラミング言語の機能では無いので、必要なメソッドの実装管理がプログラマの仕事になります。また、各表示モードが派生関係では無いのでベースクラスの変数にオブジェクトを代入することが出来ません。

インターフェイス

委譲を使った設計の場合、各表示モードのクラスに共通するメソッド「Say()」があります。このメソッドが無いと成立しないコードになっています。逆に考えるとクラス内の実装はどうあれ「Say()」メソッドがあるオブジェクトであればこのコードで利用可能です。「Say()」を実装するように強制する仕組みとしてインターフェイスがあります。それだけだとインターフェイスを記述してもしなくても良いような気がしますが、メリットとして同じインターフェイスをもつ変数に代入することができますので、同じ変数で編集モードの状態を保持するのに使えそうです。
サンプルーコード&実行結果
using System;

// 委譲&インターフェイス
interface IBaseView
{
    void Say();
}
class BaseView : IBaseView
{
    public string Value = "";
    public void Say() { Console.WriteLine("Say:{0}", Value); }
}
class SingleView : IBaseView
{
    BaseView _BaseView;
    public SingleView(BaseView baseView) { _BaseView = baseView; }
    public string Value
    {
        set { _BaseView.Value = value; }
        get { return _BaseView.Value; }
    }
    public void Say() { _BaseView.Say(); }
}
class DualView : IBaseView
{
    BaseView _BaseView;
    public DualView(BaseView baseView) { _BaseView = baseView; }
    public string Value
    {
        set { _BaseView.Value = value; }
        get { return _BaseView.Value; }
    }
    public void Say() { _BaseView.Say(); }
}
class HalfView : IBaseView
{
    BaseView _BaseView;
    public HalfView(BaseView baseView) { _BaseView = baseView; }
    public string Value
    {
        set { _BaseView.Value = value; }
        get { return _BaseView.Value; }
    }
    public void Say() { Console.WriteLine("HalfView.Say:{0}", _BaseView.Value); }
}
class Program
{
    static void Main()
    {
        BaseView bv = new BaseView{ Value = "Baaaaase", };

        IBaseView viewMode = null;

        // シングルビューモードへ変更
        viewMode = new SingleView(bv) { Value = "Single", };
        viewMode.Say();
        // デュアルビューモードへ変更
        viewMode = new DualView(bv) { Value = "Dual", };
        viewMode.Say();
        // ハーフビューモードへ変更
        viewMode = new HalfView(bv) { Value = "Half", };
        viewMode.Say();

    }
}
PS>.\Proxy      
Say:Single
Say:Dual
HalfView.Say:Half

コメント