オブジェクト指向を学ぶ姉妹 3章
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 def sayProfile(self): return "わたしは" + self.name + "だよ~" def sayAttackDamage(self): return "わたしの攻撃力は" + str(Panch(45, 32, 370).calcAttackDamage()) + "だよ~" if __name__ == "__main__": imouto = Person("妹") print(imouto.sayProfile()) print(imouto.sayAttackDamage())
わたしは妹だよ~ わたしの攻撃力は532800だよ~
妹「仮にこんな感じだとして、どう思う?」
姉『フリーザ様って呼ぶね』
妹「やめて」
姉『んーーー責任を分散してるし、問題ないんじゃない?』
妹「んとね、分散してるように書かれてるけど、実際は2つのクラスが密接に結合しちゃってるの」
姉『えええーーー』
妹「たとえば~」
- 「Panch」が存在することを、クラス「Person」は知っている
- 「Panch」に「colcAttackDamage」が存在することを「Person」は知っている
- 「Panch」の引数に「weight」「grip」「speed」が必要であることを「Person」は知っている
- 「Panch」の引数が「weight」「grip」「speed」の順であることを「Person」は知っている
妹「って感じで、PersonはPanchのことを知りすぎちゃってるの。」
姉『ふんふん。それだと何か悪いの?』
妹「例えばPanchのクラス名とか変数変えたら、Personまで修正が必要になっちゃう」
姉『んー、でもそれってしかたなくない?』
妹「ある程度は仕方ないんだけど、上のは不必要な依存ばっかなんだ~」
姉『依存関係を理解すれば、そういう修正が必要なくせるってこと?』
妹「うん!ある程度は減らせるよ~!」
姉『だいたいわかった!じゃあその回避方法教えてー!』
疎結合なコードを書く
妹「上で4つの結合ポイントを話したから、その結合ポイントを順を追って潰していくね~」
姉『じゃあ最初はこれかな?』
- 「Panch」が存在することを、クラス「Person」は知っている
姉『正直クラス名変えたら、VSCodeが勝手に修正してくれるから別に問題ないんだけどねぇ』
妹「喝っ!」
姉『喝っ!された』
妹「クラス名が変わったときだけじゃなくて、他にも上のには致命的な所があるんだよ!」
姉『えぇぇー後出しー!』
妹「例えば私が日本刀を装備したとするじゃん?」
姉『殺意のかたまり』
妹「sayAttackDamageを使って日本刀の攻撃力を出力したいのに、Panchクラスが占有しててできないの」
姉『あーたしかに。これだとパンチ力しか出力できないね』
妹「そのために、sayAttackDamageにあるPanchクラスを外に出してあげる必要があるんだ~」
姉『そうなると、どんな感じ?』
妹「ひとまずこんな感じかな~」
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, weapon): self.name = name self.weapon = weapon def sayProfile(self): return "わたしは" + self.name + "だよ~" def sayAttackDamage(self): return "わたしの攻撃力は" + str(self.weapon.calcAttackDamage()) + "だよ~" if __name__ == "__main__": imouto = Person("妹", Panch(45, 32, 370)) print(imouto.sayProfile()) print(imouto.sayAttackDamage())
姉『Personインスタンスを作成時にweaponを要求して、そこにPanchを渡してるんだね』
妹「これなら日本刀クラスができたときに、同じようにweaponに渡せるからね~」
姉『おっけー!だいぶ理解した』
妹「うん!ちなみにこのテクニックは依存オブジェクトの注入って言うんだよ~」
依存を隔離する
姉『でもなー、こういうの覚えても、結局実践で使えなかったりするんだよねぇ』
妹「わかる~~~!!!既存のコードいじるの難しいよね~」
姉『でしょでしょ。引数いじると相当修正必要になったりするし』
妹「でも放置してよくなくなることなんてないから、リファクタリングはしたいよねぇ」
姉『言うは井上やすしぃ』
妹「上のような変更はできなくても、インスタンス作成を分離したりとかからちょっとずつ!」
姉『段階を追うような修正があるの?』
妹「そう!例えば上の例で、引数をいじれなくても、こんな感じに!」
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 sayProfile(self): return "わたしは" + self.name + "だよ~" def sayAttackDamage(self): return "わたしの攻撃力は" + str(self.weapon.calcAttackDamage()) + "だよ~" if __name__ == "__main__": imouto = Person("妹") print(imouto.sayProfile()) print(imouto.sayAttackDamage())
妹「こんな感じでクラス内で分離すれば、sayAttackDamegeが綺麗になる!」
姉『でも結局Personの中でPanchを定義しちゃってるね』
妹「うん!そのうえで、さっきのに比べて依存が明らかで修正しやすいでしょ?」
姉『うーん、なるほど?』
妹「小さいプログラムだから実感しづらいけど、大きくなればわかるんだから!」
姉『はーい』
引数の順番への依存を取り除く
妹「次はこれを解決していくよー」
- 「Panch」の引数に「weight」「grip」「speed」が必要であることを「Person」は知っている
- 「Panch」の引数が「weight」「grip」「speed」の順であることを「Person」は知っている
姉『これもう仕方なくない?』
妹「ううん。まず順番のほうなんだけど、ハッシュ(連想配列)を使えば簡単に回避できるの」
姉『ハッシュポテト!好き!』
妹「Pythonで書くとこんな感じかなぁ」
姉『ガン無視!』
class Person: def __init__(self, args): self.name = args["name"] self.weight = args["weight"] self.grip = args["grip"] self.speed = args["speed"] if __name__ == "__main__": args = { "name": "妹", "speed": 370, "weight": 48, "grip": 32 } imouto = Person(args)
妹「だいぶ簡略化したけど、こうすれば引数の順番気にしなくていいでしょ?」
姉『おーすごい』
妹「さらに引数のデフォルト値を設定することで、引数の数も知らなくてよくなる!」
class Person: def __init__(self, args): self.name = args.get("name", "デフォルトさん") self.weight = args.get("weight", 100) self.grip = args.get("grip", 100) self.speed = args.get("speed", 100) if __name__ == "__main__": args = { "name": "妹", "speed": 370 } imouto = Person(args) print(imouto.name) print(imouto.weight) print(imouto.grip) print(imouto.speed)
妹 100 100 370
妹「こんな感じ!」
姉『なるほどねー!すごい!』
妹「えへへ~」
姉『でもいちいち毎回こんなの書くの面倒だなぁ』
妹「そだね~。費用対効果考えて使うべきところで使うべきだよ~!」
依存方向の管理
姉『づがでだぁ』
妹「あともうちょっとだから頑張って!」
姉『もう、ほんと、簡潔にお願い……』
妹「おっけ~!依存方向って概念について簡単にまとめるね」
姉『ありがたやぁ』
妹「今までの例だと、PersonがPanchを呼んでたよね。人はパンチをする!って感じで」
姉『うんうん』
妹「でもこの方向って逆にもできるの。パンチをするのは人だ!って感じに」
姉『ふんふん?』
妹「じゃあ実際に実装するとき、どっちで書けばいいと思う?」
姉『最初の、人はパンチをする!じゃない?』
妹「それが正解かどうかを測る基準として、以下の概念があるの」
- 変更の起きやすさ:変更がより起きにくいものに依存するべき
- 具象と抽象:より抽象化されたものに依存するべき
- 大量に依存されたクラスを避ける:言葉通り
妹「ザクっというとこんなこんな感じ」
姉『やったー簡潔!』
妹「変更が起きやすいのに依存の数が多いクラスがあったら破滅の元だよ!」
姉『はーい!』
3章まとめ
妹「今日はこんなところかな~。どう?おねえちゃん」
姉『責任を小さく作ったら、使うときも結合度を小さくするように作る!』
妹「完璧~~~!天才~~~!」
姉『えへへー、じゃあもうそろそろ実践?』
妹「まだ今進捗1/3くらいかな~」
姉『お姉ちゃん寝るねぇ』
妹「待ってよ~!一緒にがんばろ~!」
姉『妹ちゃんのやる気がなかったらとっくに折れてるなぁ』
妹「えへへ~絶対今後の糧になるからファイトだよ~!」
姉『はーい』