こむら記録

主に技術記録です

オブジェクト指向を学ぶ姉妹 4章(1)

はじめに

妹「うーん……」

姉『どうしたの?妹ちゃん』

妹「4章まで読んだんだけどね~お姉ちゃんにどう教えようかな~って」

姉『そんなに難しいの?この[インターフェース]って』

妹「ううん~インターフェース自体はそんなに難しくないよ~」

姉『えっじゃあよくない?』

妹「んとね、この本の2章あたり最初読んだとき、メッセージが重要って書いてなかった?」

姉『あーあったねぇ。ひとまずはそれは置いておいてクラスを主体に教えるって』

妹「この4章を通じて、メッセージとは何か、なぜ重要かが書かれてるんだけど……」

姉『わかりづらかった?』

妹「めっちゃわかりやすかった!」

姉『えっじゃあよくない?』

妹「ただ例を出しながら順を追って教えてくれてて、それをまるまる書くと劣化コピーだしな~って」

姉『うーん、じゃあひとまず、インターフェースについて教えてよ!』

妹「そうだね~メッセージについてはまた後で考えるね」

姉『うん!』

インターフェースを理解する

妹「えーとまずね、インターフェースって2つ意味があるんだよね~」

姉『そうなんだ』

妹「1つめはこれから学ぶ、クラス内にあるインターフェース

姉『ふんふん』

妹「2つめは5章で学ぶ、ダックタイプと呼ばれるクラス外のインターフェース」

姉『おっけー、いったん2つ目は忘れるねぇ』

妹「話が早すぎる」

姉『でもさークラス内のインターフェースって何?変数とメソッドしかなくない?』

妹「お姉ちゃんさ、javascriptとかで [private] とか [public] とか見たことない?」

姉『あー変数名とかメソッドの前につけるやつ?』

妹「あれの意味わかる?」

姉『なんか、たしか、公開範囲を決めるんだっけ?』

妹「おお~~~!あってる!お姉ちゃん天才!」

姉『とりあえずpublicってつけておけばいいんだよね?』

妹「あほちん!!!」

姉『落差激しい』

妹「まずね、 [public] ってつけられたメソッドや変数を [パブリックインターフェース] って呼ぶの」

姉『おー。じゃあ [private] は [プライベートインターフェース] ?』

妹「正解!Pythonだと、変数名とかの前に[ __ ] をつけると [private] になるよ~」

class Sample:
    def __init__(self, value1, value2):
        self.value1 = value1    # パブリックインターフェース
        self.__value2 = value2  # プライベートインターフェース


if __name__ == "__main__":
    sample = Sample("パブリック", "プライベート")
    print(sample.value1)
    print(sample.__value2)
パブリック
Traceback (most recent call last):
  File "c:/Develop/ObjectOrientedDesign/Python/SisterAttack.py", line 9, in <module>
    print(sample.value2)
AttributeError: 'Sample' object has no attribute 'value2'

妹「こんな感じ!」

インターフェースを定義する

姉『 [private] にすると外から使えなくなっちゃうんでしょ?なんでそんなことするの?』

妹「例えばなんだけど、3章でこんな感じのコードを書いたじゃん?」

class Panch:
    def __init__(self, weight, grip, speed):
        self.weight = weight
        self.grip = grip
        self.speed = speed

    def calcAttackDamage(self):
        return self.weight * self.grip * self.speed


class Person:
    def __init__(self, name):
        self.name = name

    @property
    def weapon(self):
        return Panch(45, 32, 370)

    def sayAttackDamage(self):
        return self.name + "の攻撃力は" + str(self.weapon.calcAttackDamage()) + "だよ~"


if __name__ == "__main__":
    imouto = Person("妹")
    print(imouto.sayAttackDamage())
妹の攻撃力は532800だよ~

妹「PersonクラスでPunchを使うけど、外に出せないからせめてって責任を分離したやつ」

姉『あーフリーザ様だったやつだね』

妹「たとえばここに続けて、Groveクラスをこんな風に書きました」

class Panch:
    # 前略
    def calcAttackDamage(self):
        return self.weight * self.grip * self.speed

class Person:
    # 前略
    @property
    def weapon(self):
        return Panch(45, 32, 370)
    # 後略

class Grove:
    def __init__(self, ounce):
        self.ounce = ounce
        self.person = Person()

    def calcGroveDamage(self):
        return (1 / self.ounce) * self.person.weapon.calcAttackDamage()


if __name__ == "__main__":
    # 前略
    grove = Grove(24)
    print(grove.calcGroveDamage())
22200.0

妹「これみてどう思う?」

姉『GroveがPersonに依存してるけど、Personって変更激しそうだからよくない!』

妹「うんうん、3章がよく身についてるね~。他には?」

姉『んー……。感覚的にはGroveがPersonを経由してPunchにアクセスしてるのが気持ち悪い……』

妹「!!!その感覚すごい大事だよ!おねえちゃん!」

姉『でもGrove側でPersonと同じようにPunchを呼び出すのもDRY原則に反しちゃうしなぁ』

妹「いい感じに単一責任の考えが身についててわたしちょっと泣きそう」

姉『どうしたらいいの?』

妹「これの答えを出すのは今は置いておいて、そもそもの話するね~」

姉『うん』

妹「まず、ほんとはPersonクラス内でPunchクラスを参照したくないの」

妹「でもオブジェクト注入ができないから仕方なくクラス内に書いてます」

妹「ただ想いとしては別のところに書きたい。修正したい箇所なの」

妹「そんなことは露知らず、GroveはPersonからPunchを利用してしまった。」

姉『あー』

妹「こうなるともう、PersonからPunchの参照を取り出すのが難しくなってこない?」

姉『なんとなくわかった』

妹「そう。だから今回の場合、Person内のweaponの記述は、[private]で書くべきなの」

姉『外から使えなくすることは、余計な依存を増やさないって利点があるんだねぇ』

妹「そうそう。プログラムの世界では、知らない・使えないこともひとつの価値なんだよ~」

パブリック、プライベートの分け方について

姉『ひとまずおっけー!変更されやすいものはプライベートインターフェースにするね!』

妹「あ、それ以外にもパブリック、プライベートを分ける目安があるから教えちゃうね」

姉『あ、知りたい知りたい』

妹「まずはパブリックインターフェースについてだね~」

パブリックインターフェース

  • クラスの主要な責任を明らかにする
  • 外部から実行されることが想定される
  • 気まぐれに変更されない
  • 他者がそこに依存しても安全
  • テストで完全に文書化されている

姉『パブリックインターフェースはあんまり変更しちゃダメなんだねぇ』

妹「自由に使われた後に変更しちゃうと壊滅するのは今まで見てきた通り」

姉『うんうん』

妹「2章で単一責任の考え方に、責任の説明が一言でできることってあったよね?」

姉『言ってたかも?』

妹「基本的にその責任が、クラスのパブリックメソッドになってるはずなの」

姉『なるほどねぇ』

妹「そうやってインターフェースを整理すれば、自然と綺麗な依存関係ができあがるよ!」

姉『はーい』

妹「次はプライベートインターフェースだね~」

プライベートインターフェース

  • 実装の詳細に関わる
  • 他のオブジェクトから送られてくることは想定されていない
  • どんな理由でも変更され得る
  • 他者がそこに依存するのは危険
  • テストでは言及さえされないこともある

妹「ちなみにプライベートインターフェースも、外からアクセスできちゃうことは覚えておいてね」

姉『えっじゃあ意味ないじゃん』

妹「普通に書くとアクセスできないから、意味ないことはないよ~」

姉『でも書き方変えたらアクセスできちゃうんでしょ?』

妹「非推奨だし、書いた人にとってはアクセスさせたくないって想いが伝わるでしょ?」

姉『なんでアクセスできちゃうの?』

妹「仕様は変わるからだね~。今必要なくても後々外で使う必要ができることもあるでしょ~」

姉『あー。なるほどねぇ』

妹「クラス内のインターフェースについてはこんな感じかなぁ」

デメテルの法則

妹「それからさっきね、お姉ちゃんすごくいいこと気づいてたよ!」

姉『ほえ?』

妹「感覚的にはGroveがPersonを経由してPunchにアクセスしてるのが気持ち悪い」

姉『あーうん、最初のコードに対する感想?』

妹「そう!それね、デメテルの法則そのもの!」

姉『名前からパッと内容がわからない法則とかやめてほしいなぁ』

妹「デメテルの法則を要約すると、 [直接の友達とだけ話すこと] 」

姉『デメテルさんは根暗』

妹「さっきのはPersonを介してPunchと話してたでしょ?そういうのやめてねってこと」

姉『友達の友達は敵理論ってことだね!』

妹「ま~その認識でもいいのかな~?」

姉『デメテルさんとはいい酒が呑めそう』

妹「デメテル、人じゃなくて豊穣の女神だからね?」

姉『女神が根暗!そそるー!』

ここまでのまとめ

妹「ひとまずクラス内のインターフェースについては説明おしまい!」

姉『んー……』

妹「なにか引っ掛かるところあった?」

姉『結局、GroveクラスがPunchクラスを使う方法がわからなかったなーって』

妹「それの正解を導くには、まずメッセージについて知る必要があるの~」

姉『あー、最初に何か言ってたね』

妹「メッセージについての説明方法も思いついたから、ちょっとまとめるね~」

姉『うん!妹ちゃんいつもありがとねぇ』