bonotakeの日記

ソフトウェア工学系研究者 → AIエンジニア → スクラムマスター・アジャイルコーチ

檜山正幸のキマイラ飼育記 - クラス、オブジェクト、型; なんだか変じゃない? -の練習をやってみる。

「この継承は変な感じ」

気持ち悪い理由。…Point3D じゃなく Poit3D だから、というのは嘘で :D (以降勝手に修正して書きます)
Point3D extends Point2D としたとき、たとえば Point3D.moveTo() というメソッドは

class Point3D extends Point2D {

  void moveTo (double x, double y, double z) {
    ...
  }

}

と、3引数を取るべきで、単純なオーバーライドではうまくいかなくなります。他の、Point2D オブジェクトを操作していたメソッドには、一様にこの問題が発生する気が。
ColoredPoint2D extends Point2D では、こういう問題は起こらないです。たぶん。

「こういう型をクラスで定義したいんだけど」

はて何だろう。
うまく定義できるか、という問いに答えてませんが、NonNegativeInteger を定義すると、引き算オペレータとかで結果が負になる(NonNegativeInteger で扱える範囲を超える)場合どうするんですかね。

「こういう型達の関係は」

unionなりvariant型なりが使えるなら、そっちを使うのが筋なんでしょうけど。
この場合は、number と handle の扱いは次のようにするとか。

  1. Number か Handle かのどちらかをとる列挙型 ID を持つメンバ which を加える
  2. ID型の1引数を取るコンストラクタを定義し、これを使って、そのインスタンスの which の値を決定する。
  3. アクセッサメソッドに which の判定を仕込む事で、セットの制限を行う。

UserNumber と UserHandle は、コンストラクタでbaseクラスのIDを自動的にセットするように仕込むようにして、後は普通に継承。


実際に、C#でコード書いてみました。重要なところだけ抜粋。

    /// <summary>
    /// number か Handleかを決定する列挙型
    /// </summary>
    enum ID { Number, Handle };


    /// <summary>
    /// UserID クラス
    /// </summary>
    class UserID
    {
        protected int number;
        protected String handle;
        protected ID which;

        /// <summary>
        /// number へのアクセッサメソッド
        /// </summary>
        public virtual int Number
        {
            get 
           { 
                if (which == ID.Number)
                    return number;
                else 
                    Error(); // Error() の中身は適当に
           }
            set
            {
                if (which == ID.Number)
                    number = value;
                else
                    Error();
            }
        }

        /// <summary>
        /// handle へのアクセッサメソッド
        /// </summary>
        public virtual String Handle
        {
            get 
            {
                if (which == ID.Handle)
                    return handle;
                else
                    Error();
            }
            set
            {
                if (which == ID.Handle)
                    handle = value;
                else
                    Error();
            }
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="select">IDの設定</param>
        public UserID(ID select)
        {
            which = select;
        }

        /// <summary>
        /// IDを表示する。
        /// </summary>
        public void Show()
        {
            switch (which) {
                case ID.Number:
                    Console.WriteLine("Number: " + number.ToString());
                    break;
                case ID.Handle:
                    Console.WriteLine("Handle: " + handle);
                    break;
            };
        }
    }

    /// <summary>
    /// UeerNumberクラス::コンストラクタの定義のみ。
    /// </summary>
    class UserNumber : UserID
    {
        public UserNumber() : base(ID.Number) { }
    }

    /// <summary>
    /// UserHandleクラス::コンストラクタの定義のみ。
    /// </summary>
    class UserHandle : UserID
    {
        public UserHandle() : base(ID.Handle) { }
    }

    /// <summary>
    /// Mainプログラム
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            UserHandle handleID = new UserHandle();
            handleID.Handle = "Takeo";
            UserNumber numberID = new UserNumber();
            numberID.Number = 5;

            handleID.Show();
            numberID.Show();
        }
    }

「どっちに味方しますか」

あーうー、どっちなんでしょうねぇ。今までの問題に共通した話ですね、これ。
でも、「基本クラスにあるメソッドは、派生クラスにもある」と考えるのが原則ですよね。(C#の sealed 修飾子のような物をつけない限り。)機能をつぶす方面で考えると、do-nothingにオーバーライドしたキーボードイベントハンドラや、number / handle の例のアクセッサメソッドのように、どうしても機能しないメソッドが残ってしまうのが気持ち悪いですね。
ということで心持ちAさんを応援。



あー、全然ドライじゃないや。もっとちゃんと考えるべき??

注:bonotakeは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。