オブジェクト指向プログラミングを極める

より良いプログラマを目指すブログ

privateメソッドは不純物

あるクラスの中で同じ処理が何度も出てくることがある。
このときコードの重複を排除するためにprivateメソッドを使うことを思いつく。

    class SomeDevice
    {
        // デバイスの温度を取得
        public float GetTempareture()
        {
            byte[] rawData = input.read();
            return ParseToShort(rawData) - 273.15f;
        }

        // デバイスの電圧を取得
        public float GetVoltage()
        {
            byte[] rawData = input.read();
            return ParseToShort(rawData) * 1000 / (float)0xFFFF;
        }

        // 2byteの生データをushortに変換
        private ushort ParseToShort(byte[] rawData)
        {
            return (ushort)((rawData[0] << 8) | (rawData[1]));
        }
    }

さらに他のクラスでも同じprivateメソッドが現れたとする。

    class AnotherDevice
    {
        // デバイスの角度を取得
        public float GetLotation()
        {
            byte[] rawData = usb.read();
            return ParseToShort(rawData) / (float)0xFFFF;
        }

        // デバイスの速度を取得
        public float GetSpeed()
        {
            byte[] rawData = usb.read();
            return ParseToShort(rawData) * 100 / (float)0xFFFF;
        }

        // 2byteの生データをushortに変換
        private ushort ParseToShort(byte[] rawData)
        {
            return (ushort)((rawData[0] << 8) | (rawData[1]));
        }
    }

ここでprivateメソッドが重複してしまうので抽象クラスにまとめる。

    abstract class Device
    {
        protected ushort ParseToShort(byte[] rawData)
        {
            return (ushort)((rawData[0] << 8) | (rawData[1] & 0xFF));
        }
    }

    class SomeDevice : Device {...}
    class AnotherDevice : Device {...}

コードの重複は避けられた…が、これはまったくエレガントではない。他のデバイスが2バイト以外のデータを返す場合は結局別のメソッドを作らないといけなくなるし、だいいち私の嫌いな継承が使われている。最良の方法はこのprivateメソッドをオブジェクトにしてしまうことだ。

    class UShortFromBytes
    {
        readonly byte[] rawData;

        public UShortFromBytes(byte[] rawData)
        {
            this.rawData = rawData;
        }

        public ushort Value()
        {
            return (ushort)((rawData[0] << 8) | (rawData[1]));
        }
    }

    class SomeDevice
    {
        // デバイスの温度を取得
        public float GetTempareture()
        {
            byte[] rawData = input.read();
            return new UShortFromBytes(rawData).Value() - 273.15f;
        }

        // デバイスの電圧を取得
        public float GetVoltage()
        {
            byte[] rawData = input.read();
            return new UShortFromBytes(rawData).Value() * 1000 / (float)0xFFFF;
        }
    }

この方法は抽象クラスにまとめる方法よりも多くの利点がある。まず分離したオブジェクトは単体テスト可能である。さらにポリモーフィズムにより拡張可能である。そして何より分離後のクラスは小さくてシンプルなものだ。privateメソッドが現れた場合、早いうちにそれをオブジェクトとして独立できないか検討するべきだと思う。特に同じ内容のprivateメソッドが複数のクラスに渡って存在する場合、それは間違いなくオブジェクトとして分離すべきなのでどんどんオブジェクト化していきたい。

privateメソッドはオブジェクト指向とは関係ない?

オブジェクト指向におけるメソッドとは、特定のメッセージに反応して処理されるコードブロックのことらしい。それならばprivateメソッドとはいったい何なのだろうか。thisを通してしか呼び出せないprivateメソッドは自分自身へのメッセージのみに反応して処理されるコードブロックということになる。オブジェクト指向プログラミングは「オブジェクト同士」によるメッセージのやり取りでプログラムを表現するもの、ということから考えるとprivateメソッドにはコードを読みやすくする以上の役割はない。以上のことからprivateメソッドはオブジェクト指向とは関係のない不純物と言えるだろう。真のオブジェクト指向プログラムでは、メソッドはpublicのみが存在するべきである。