#9 オブジェクト (なにがそれをするのか)

自然な考えで

物事というのは誰かが何かをしたり、あるいは何かに何かをしたりするものです。

プログラムにおける関数は「動詞」にあたるものです。 例えばopenという関数を考えてみましょう。

open();

一般的にopenという関数はファイルを開くものですが、「開く」という動詞は別にファイルだけのものではありません。

例えばあなたはゲームを作っているとします。 ゲーム上ではドアを開く必要があるでしょう。しかし、単純なopen関数は既に使われてしまっています。 では、open_door関数を作るのでしょうか。

open_door();

これはなかなか長くなっていきそうです。

open_room14_door8_with_key2();

あるいは処理を判定するのでしょうか。

sub open_something() {
    my ($action = shift(@_));
    if ($action == "door") {
        open_door(@_);
    } elsif ($action == "open_room14_door8_with_key2") {
        open_room14_door8_with_key2();
    }
}

これはこれで不毛なコードがどんどん長くなりますし、わかりづらくなっていきます。 わかりづらいということは、バグへの道を歩んでいるのだといえます。

またここで使われているshiftリスト変数の先頭からひとつ値を取り出すものであり、引数はリスト変数だと決まっています。

それならば、shiftという名前はリスト変数に属しているべきではないでしょうか。 shift自体はリスト変数に属しているということは示されていません。ただ、プログラマーが「shiftは引数にリスト変数を取る」と覚えていることが便りであり、もし異なった書き方をすればうまく動きません。

オブジェクト指向は動作は、その動作を行う主体に属します。 これは主語・述語の関係ですが、どちらかといえば日本語の、というよりは英語の主語に近いものです。1

例えばドアを開くなら次のように書くことができます。

door.open

同じようにファイルを開くなら次のように書けるでしょう。

File.open

オブジェクト指向

オブジェクト指向は見た目上は手続き型において第一引数になることが多い「何が」あるいは「何を」というものが左側にくるものと考えられます。

shift(@mylist);
@mylist.shift();

ただし、考え方が大きく違います。 手続き型ではあくまで動作が中心にあり、その動作になにを与えるかを示します。 イメージとしては、謎の「shiftマシーン」があり、shiftマシーンに配列を投げ込めば配列がshiftされる、というわけです。 投げ込むこと自体はなんでもできますが、shiftマシーンが受け付けないものを投げ込んだらshiftマシーンはおかしなことになってしまうかもしれません。

対してオブジェクト指向では「何が」ということを中心に考えます。 shiftマシーンがあるわけではなくて、shiftという動作があります。別の見方をしてみましょう。

Johnは素晴らしいスノーボーダーです。

john = SnowBorader.new

Bobは魅力的なシンガーです。しかし、運動はちょっと苦手です。

Bob = Singer.new

Johnはもちろん華麗なオーウェン(というスノーボードの技)を決めることができます。

john.owen

しかしBobはそんなことはできません。Bobにオーウェンをさせようとするとエラーになってしまいます。

bob.owen
Traceback (most recent call last):
-e:3:in `<main>': undefined method `owen' for #<Singer:0x000055be20fa5670> (NoMethodError)

私達が日常的に使う概念としては「何が、どうする」というふうに考えるほうが自然です。 オブジェクト指向はコンピュータ的に有利なわけではなく、人間が自然な概念で、わかりやすく自然に書けるようにするためにあります。

クラスと継承

クラスはオブジェクトがどのようなのもかを定義します。

Doorクラスはドアという概念であり、一方Doorオブジェクト(=Doorクラスのインスタンス)は1枚のドアです。

クラスにはオブジェクトがどのような動作や値を持っているか、ということを定義します。

class Door
  def initialize
    @opened = false
  end

  def knock
    puts "あなたはドアをノックした"
  end

  def open
    if @opened
      puts "ドアは既に開いている"
    else
      @opened = true
      puts "あなたはドアを開いた"
    end
  end
end

ここで定義したDoorクラスを使ってドアを作っていきます。

door1 = Door.new
door2 = Door.new

さらにDoorクラスの性質を持っているものの、Doorクラスと全く同じではないドアもあることでしょう。 例えば、鍵のかかったドアです。このドアは普通のドアと同じようにノックしたり。開けたりすることはできますが、単に開けたら単純に開くわけではありません。 そこで、Doorクラスを継承したサブクラスのLockedDoorクラスを作りましょう。

class LockedDoor < Door
  def initialize(key)
    super
    @locked = true
    @key = key
  end

  def unlock(key)
    if key == @key
      @locked = false
      puts "あなたは鍵を開いた"
    else
      puts "あなたは鍵をひらくことができなかった"
    end
  end

  def open
    if @locked
      puts "扉には鍵がかけられている"
    else
      super
    end
  end
end

初期化を行うinitializeメソッドで、鍵が閉まっているという状態と、そのドアの鍵を登録するようになりました。 その前にsuperメソッドDoorクラスの初期化メソッドを呼び出していますから、@openedインスタンス変数も設定されています。

unlockメソッドLockedDoorクラスで追加されたメソッドです。

一方、openメソッドは鍵がかかっているかどうかをチェックする機能が追加されました。 鍵がかかっていなければDoorクラスと同じことですので、superを呼び出します。


  1. 日本語では目的語になるような場合でも英語では主語となる場合が多いです。↩︎