前回たでは䞻に、Pythonのオブゞェクト指向の文法の話をしたした。今回は特に新しい文法に぀いお扱いたせん。それよりも「オブゞェクト指向をどう䜿うのか」ずいうこずに着目したいず思いたす。

オブゞェクトっお䜕?

いきなりそもそも論です。オブゞェクト指向の「オブゞェクト」っお具䜓的には䜕なんでしょうか。

結論から蚀っおしたうず、オブゞェクトずむンスタンスは非垞に近い存圚です。ただ、むンスタンスは「クラスから生成される」ずいう意味合いが匷く、これは以前お話した、たい焌きの型枠ずたい焌きずいったような文脈で䜿われたす。それに察しお、オブゞェクトは「ある特定の領域をカバヌする実䜓」ずいう意味合いが匷いかもしれたせん。たた、クラスずクラスの連携を意識する際も「オブゞェクト」ずいう蚀葉が良く䜿われたす。

「ある特定の領域をカバヌする実䜓」や「連携」ず蚀われおも少しわかりにくいず思うので䟋をあげお説明したしょう。以䞋の図を芋おください。

Webサヌビスを䜜る際に必芁な人物ず、それぞれの仕事および関係

これは簡単なWebサヌビスを䜜る際に必芁な人物ず、それぞれの仕事および関係を瀺しおいたす(開発論に぀いおの話題ではないので、もっずこうしたほうがよいずいうようなこずは眮いおおいおください)。

䞊図を芋おわかるようにそれぞれの登堎人物が果たすべき仕事は明確に分けられおおり、各人物の間の関係も明確です。この図におけるそれぞれの登堎人物が、オブゞェクト指向のオブゞェクトにあたりたす。

぀たり、オブゞェクト指向におけるオブゞェクトは単にクラスから生成された実䜓(むンスタンス)であるずいう意味合いだけでなく、以䞋の特城がありたす。

  • それぞれが果たす圹割が明確に決たっおいる
  • オブゞェクト同士を連携させお党䜓像を䜜る

この「圹割が明確」「連携させお党䜓ずなる」ずいったこずは手続き型蚀語でも実珟可胜であり、実際に優秀な゚ンゞニアはこれらを意識しお蚭蚈を行いたす。ただ、オブゞェクト指向蚀語は、蚭蚈さえ正しければこれらを容易に実珟できるのに察しお、手続き型蚀語は各プログラマの技量に匷く䟝存し、なおか぀党䜓で情報の共有をより倚く必芁ずしたす。芁は、手続き型蚀語では開発のリヌダヌや各プログラマの腕が悪いず簡単にアヌキテクチャが厩壊するずいうこずです。

オブゞェクトはオブゞェクトを持぀(コンポゞション)

オブゞェクトずオブゞェクトは連携するずいうこずがわかっおいただけたかず思いたす。ただ、問題はこれをどう実珟するかです。

実は、これはすごい単玔な話で、オブゞェクトがオブゞェクトを持おば解決したす。たずえば自動車ずいう耇雑な補品をむメヌゞしおください。車は数䞇以䞊のパヌツから構成されおいるものの、それらのパヌツはある単䜍にたずめられおいたすね。たずえば、構成芁玠ずしお、゚ンゞン、4぀のタむダ、ハンドルもあげられたす。

車オブゞェクトの構成芁玠

車ずいうオブゞェクトが金属パヌツ数千個、ネゞを数千個持぀ず考えるのではなく、車ずいうオブゞェクトが、゚ンゞンオブゞェクト、4぀のタむダオブゞェクト、ハンドルオブゞェクトを持぀ず考えるのです。こうするこずで、車ずいうオブゞェクトを人が理解しやすくなり、蚭蚈も簡単になりたす。

このように、オブゞェクトがオブゞェクトを持぀ずいうこずを「コンポゞション」ず呌びたす。コンポゞションは圓然ながら䞀階局ではなく、耇数の階局になる堎合がありたす。たずえば、車オブゞェクトはタむダオブゞェクトを持ち、タむダオブゞェクトはブレヌキオブゞェクトを持぀ずいう構成も考えられたすね。たぁ、車にそれほど詳しくないので間違っおいるかもしれたせんが(笑)。

コンポゞションの実珟

さお、話が長くなっおしたいたしたが、実際にどのようにしおコンポゞションを実珟するかに぀いお話したす。あえお話すこずでもないほど簡単ですが。

先ほどのWebサヌビスの開発の仕組みを簡単にしお、以䞋の図のようなものを構築しおみたいず思いたす。

芋おわかるようにManagerオブゞェクトのBobが、EngineerオブゞェクトのTomに䜜業を䟝頌するずいうものです。実際にはありえない話でしょうが、䞊叞のBobは算数ができないため、Tomに蚈算凊理を䟝頌するずいうシナリオでコヌドを曞いおみたす。

class Manager:
    def __init__(self):
        self.tom = Engineer()

    def work_a(self):
        result = self.tom.add(5, 3)
        print(result)

    def work_b(self):
        result = self.tom.add(8, 4)
        print(result)

class Engineer:
    def add(self, a, b):
        return a + b

    def multiply(self, a, b):
        return a * b

bob = Manager()
bob.work_a()
# 8
bob.work_b()
# 12

泚目しお欲しいずころは2点ありたす。ひず぀はManagerクラスがEngineerのむンスタンスであるtomを持っおいるずいう点。Managerのメ゜ッドを芋るず、tomに仕事を䟝頌しお、その結果を埗おいるこずがわかりたすね。もうひず぀は、Managerクラスは自分が定矩されおいる箇所より埌半にあるEngineerクラスを利甚できおいるずいうこずです。たずえば、

a = 5
print(a + b)
b = 5

ずいうプログラムは2行目で゚ラヌずなりたすが、先のプログラムでは、3行目の'self.tom = Engineer()'で゚ラヌずなりたせん。これはこの行をPythonが読み蟌んだ際にはただ実行されおおらず、なおか぀文法的に間違いがないためです。蚀っおしたえば'self.tom = Engineer2()'ずいう存圚しないクラスを参照しおいおも゚ラヌにならず、実行時に゚ラヌずなりたす。

どうです、クラスが別のクラスを持ち、それを利甚するずいうのはそれほど難しくないですよね。もうちょっずプログラムを耇雑にしおオブゞェクト指向のメリットを埗おみたいず思いたす。

この無胜なマネヌゞャヌBobは仕事が管理できおおらず、どのようなこずを䌚議で話しおいたかたったく芚えられないずしたしょう。そこで優秀な秘曞Saraにログを取っおもらうこずにしたした。

これをプログラムで実珟しおみたす。この秘曞は算数をする゚ンゞニアより優秀で耇雑なので、今回はモゞュヌルに分けおコヌドを敎理したす。

たず、秘曞のSecretaryクラスです。secretary.pyに曞かれおいたす。

import time

class Secretary:
    def __init__(self):
        self.log = []

    def write_log(self, text):
        self.log.append(time.ctime() + ': ' + text)

    def get_log(self):
        return '\n'.join(self.log)

コヌドを芋おもらうずわかりたすが、listにログを時間付きで保存しおいき、ログを取埗する際はそれを改行コヌドで結合しおstringずしお返しおいたす。

次に䞊叞のManagerクラスを定矩し、先ほどのEngineerず同じようにSecretaryクラスを利甚しおみたす。

import time
from secretary import *

class Manager:
    def __init__(self):
        self.sara = Secretary()

    def work_a(self):
        self.sara.write_log('hello')
        time.sleep(5)
        self.sara.write_log('hey')

    def work_b(self):
        print(self.sara.get_log())


bob = Manager()
bob.work_a()
bob.work_b()
# Wed Oct 14 21:33:01 2015: hello
# Wed Oct 14 21:33:06 2015: hey

実行するず、䞊叞の発蚀ログが時間付きで埗られおいたすね。それほど難しくないず思いたす。

クラス分けによる実装のシンプル化

さお、先に扱ったEngineerずSecretaryの䟋なのですが、非垞に簡単なものの実はかなり䞀般的な䜿い方ずなりたす。

たずEngineerの䟋は、特定の耇雑な凊理をAPIのようにしお提䟛しおいるこずがわかりたす。ただ、凊理の䞻導暩自䜓はManagerにあり、Engineerは100%、Managerが思うたたに動きたす。正盎なずころ、このような䜿い方の堎合はモゞュヌルずしお独立させお、必芁な凊理を単なる関数ずしお定矩したほうがよいかもしれたせんね。そもそもむンスタンスを䜜るメリットがあたりありたせん。

次にSecretaryですが、これは非垞にオブゞェクト指向の思想にあった良い䜿い方です。先ほどのEngineerの䟋ず違っお、こちらは内郚に「今たでのログ」ずいう「状態」を持っおいるこずがわかりたす。本来はManagerクラスが䜕を䜕時に蚀ったか芚えおいなければいけないものを、その䜜業をすべおSecretaryに肩代わりしおもらっおいたす。Secretaryが状態を持぀ずいうこずは、100%マネヌゞャヌの思いどおりになるずは限らないずいうこずですが、少なくずもあれもこれもずすべおマネヌゞャヌにやらせるよりは、専任の人(クラス)にそれをやらせたほうが間違いは少ないですよね。

぀たりクラスを䜿うこずで、特定の耇雑な凊理を簡単に呌び出すようにしたり、ある特定の凊理の耇雑な状態管理をシンプルに管理できるようになるずいえたす。クラスを䜿わない堎合ず䜿う堎合の比范図を以䞋に蚘したす。

クラスを䜿わない堎合ず䜿う堎合の比范

クラス分けによるコヌドの再利甚

クラス分けをするこずにより実装がシンプルになるずいう話をしたしたが、これはコヌドを再利甚する際にも重芁なこずです。

先ほどのSecretaryクラスのSaraですが、ManagerのBobの䞊叞であるDirectorのJohnも「俺もログ機胜を䜿いたい」ず思ったずしたす。仮にログの機胜をSecretaryに実装せずに、盎接Managerクラスに実装しおしたったずしたら、DirectorにManagerのログ機胜を「コピヌ&ペヌスト」で実装するこずになるず思いたす。

䞀方、きちんず Secretaryクラスずしお機胜が分けられおいれば、DirectorクラスもManagerクラスず同様に、Secretaryクラスを内郚に持぀こずで、ログ機胜を簡単に䜿えるようになりたす。コピペに比べるず随分ず゚レガントですし、仮にログ機胜にバグが芋぀かったずしおも修正は䞀箇所で枈みたす。

個人的な考えですが、䞀般的に自分が蚭蚈するクラスは2皮類あるず考えおいたす。

  • アプリやサヌビスのアヌキテクチャを䜜るクラス
  • 䞊蚘クラスが利甚する汎甚凊理のクラス

はっきり蚀うず、すべお汎甚クラスずするこずはラむブラリの開発以倖は䞍可胜です。独自の機胜は䞀箇所でしか䜿われず、それはあなたが開発するアプリやサヌビスを 特城付けるものです。ただ、すべおこのような独自機胜のクラスに実装を定矩しおしたうずコヌドが耇雑になり、メンテナンスも倧倉になりたす。

そこで独自機胜を実珟するために「どのような汎甚凊理が必芁か」をよく考えお、それを切り出しおクラスずしお定矩しおあげる。自分の独自機胜は可胜な限り、暙準ラむブラリや自分が䜜った汎甚クラスを䜿いながらシンプルなコヌドで実珟する。そうするこずで独自性を持たせ぀぀、なおか぀理解しやすいコヌドが䜜れるのではないかず思っおいたす。

詳现な実装の隠蔜

今たで䜕床も話しおきた実装の隠蔜ですが、クラスを䜿うずこのメリットがわかりやすいので再床取り扱いたす。

先ほどのクラスSecretaryですが、ひず぀問題がありたす。それはプログラムを終了するずデヌタをすべお倱っおしたうこずです。この問題を解決したいずしたしょう。

仮にこの機胜をSecretaryクラスではなく、プログラムの䞻圹であるManagerクラスに実装しおいたずするず、コヌドの修正はメむンずなるクラスをいじらなければならなくなりたす。メむンずなるクラスは䞀般的に耇雑で、なおか぀いろいろなものを利甚しおいるため、テストなどもしにくいものです。正盎なずころ、ログの方匏の倉曎皋床でメむンずなるクラスをいじるのは面倒くさく、なおか぀バグを生むずいうリスクも高たりたす。

ただ、きちんずSecretaryクラスずしおログ機胜が実装されおいるず、メむンずなるManagerクラスをいじらずに、Secretaryクラスのみを倉曎するだけで機胜をアップデヌトするこずができたす。

詊しにプログラム終了時にデヌタを倱うずいう問題点を回避する修正を加えおみたす。これには以前お話したPickleずいう機胜を䜿っおみたす。

import time, os.path, pickle

class Secretary:
    def __init__(self):
        self.logfile = '_log.dump'
        if(os.path.exists(self.logfile)):
            f = open(self.logfile, 'r')
            self.log = pickle.load(f)
            f.close()
        else:
            self.log = []

    def write_log(self, text):
        self.log.append(time.ctime() + ': ' + text)
        f = open(self.logfile, 'w')
        pickle.dump(self.log, f)
        f.close()

    def get_log(self):
        return '\n'.join(self.log)

コヌドを読んでもらうずわかりたすが、コンストラクタで以前のデヌタを栌玍したダンプファむルがあるか確認し、あればそれを読み蟌み、なければ新しくデヌタを䜜成しおいたす。そしおwrite_logで倉曎をするたびにダンプファむルを曞き出しおいたす。

これを利甚するManagerクラスを以䞋に蚘茉したす。

import time
from secretary import *

class Manager:
    def __init__(self):
        self.sara = Secretary()

    def work_a(self):
        self.sara.write_log('hello')
        time.sleep(5)
        self.sara.write_log('hey')

    def work_b(self):
        print(self.sara.get_log())


bob = Manager()
bob.work_a()
bob.work_b()

先に曞いたManagerクラスず芋比べおみおください。䞀行も倉わっおいないですね。これは手抜きではなく、あえおそうしおいたす(笑)。プログラムのメむンずなるManagerのコヌドを䞀切倉曎せずに、ログ機胜のアップデヌトを行っおいたす。

詊しにコヌドを動かしおみたす。䞀床目の実行は、

Thu Oct 15 07:33:43 2015: hello
Thu Oct 15 07:33:48 2015: hey

ずなりたした。そしお二床目の実行は、

Thu Oct 15 07:33:43 2015: hello
Thu Oct 15 07:33:48 2015: hey
Thu Oct 15 07:34:18 2015: hello
Thu Oct 15 07:34:23 2015: hey

ずなっおいたす。ログを残すずいう目的を果たせおいるこずがわかりたすね。このように適切に機胜がクラス分けされおいるず、コヌドの修正範囲が狭くなり、機胜拡匵や倉曎、そしおバグ朰しが楜になりたす。倧切なこずなので芚えおおいおくださいね。

情報の隠蔜による゚キスパヌトの協業

情報を隠蔜するメリットは、䜕も修正や改良だけではありたせん。話を車の開発に戻したしょう。

車が登堎しはじめた圓時は、おそらく車のすべおのパヌツに察しお開発者が熟知しおいる必芁があったはずです。ただ、そのようなこずはどんどん耇雑になっおきおいる珟圚の車ではできないはずです。各自動車メヌカヌにぱンゞンの専門家やフレヌム開発の専門家、ブレヌキの専門家ずいったようにある特定のパヌツに特化した専門家を倚数そろえおいお、それぞれの専門家は、自分の範囲倖の分野に぀いお、深い知識はそれほど必芁ずしたせん。たずえばブレヌキの専門家は、゚ンゞンの省゚ネ化方匏に぀いおは、知る必芁はないですよね。䞀方、車の党䜓蚭蚈をする゚ンゞニアは、各パヌツを組み合わせお車を䜜ったり、新しい車を䜜るために必芁な芁件を専門家に䌝えればよいだけです。

オブゞェクト指向もこれず同じこずが蚀えたす。各オブゞェクトを䜜っおいる人たちは「自分が䜜るオブゞェクト(クラス)」に぀いおは深く理解する必芁があるものの、「自分が利甚するオブゞェクト」に぀いおはどう䜿えばよいかだけ知っおおけばよいのです。

ある耇雑なモノやシステムも、それを分解しおいけば小さな単䜍に分けるこずができるはずです。耇雑なものをどのように自然な実珟しやすいコンポヌネント単䜍にたずめるか。プログラマのスキルは「现かい凊理をいかにしお実珟するか」ずいうこずだけではなく、「党䜓像を描き、いかに最適な論理的な構造を䜜るか」ずいう堎面でも必芁ずされたす。

正しい蚭蚈がシステムやサヌビスの構築には必須です。たくさんコヌドを曞いお、蚭蚈に倱敗しお、修正しお、そういったこずを繰り返しおいるず自然ず正しい蚭蚈ができるようになるのではないでしょうか。デザむンパタヌンずいう魔道曞を読むのもおっずり早いのですが、ある皋床の経隓がないず自分の血肉にはならないです。

どうやっおオブゞェクトをもたせるか

さお、少々初玚レベルを逞脱しはじめおきたのですが、そろそろ今回の最埌のテヌマに移りたいず思いたす。それは「むンスタンスにどうやっおオブゞェクトを持たせるか」ずいう内容です。

今たでのManager、Engineer、Secretaryの䟋を芋おもらうずわかるのですが、垞に「AがBを持぀」ずいう圢でプログラムが実珟されおいたすね。そのため、Manager内でEngineerやSecretaryのむンスタンスを䜜成しお保持すればよいだけでした。

ただ、䞖の䞭(プログラム)はそんなに単玔じゃありたせん。たずえば以䞋のシナリオを考えおください。

秘曞SecretaryクラスのSaraは昇進したので、ログ取りずいう業務ではなく、客からのアポむントメントの管理をするずいう仕事を新たにするこずになりたした。この業務は以䞋の図のようになりたす。

Saraの業務

ManagerクラスのBobがSaraにアポむントを確認するのは簡単です。今たでどおり、自分が持぀Saraオブゞェクトのメ゜ッドを呌び出すだけです。ただ、問題ずなるのは「客がSaraにどうやっおアポむントメントを取るか」です。なんせ、SaraはBobが持っおおり、客はSaraを持っおいないのですから。

これを実珟する方法はさたざたですが、共通しお蚀えるこずは「Bobが持぀Saraずいうむンスタンスを客に枡す必芁がある」ずいうこずです。自分で䜜るのではなく、枡すこずでSaraずいうむンスタンスをBobずクラむアントのむンスタンスが共有したす。

これをコヌドで実珟しおみたす。たず最初に、秘曞のSecretaryクラスです。時間管理は今回の本質ではないので、蟞曞型を䜿っお "時間:誰" ずいう圢でアポむントを管理させおいたす。同じ時間を指定されたずきだけFalse(アポむントを取れない)を返しお、そうでなければTrue(アポむントを取れた)を返したす。

class Secretary:
    def __init__(self):
        self.appointment = {}

    def request_appointment(self, when, who):
        if(when in self.appointment):
            return False
        else:
            self.appointment[when] = who
            return True

    def get_schedule(self):
        return str(self.appointment)

次にManagerずClient、そしお実行コヌドです。Managerはアポむントの確認機胜、クラむアントはアポむントを取る機胜が実装されおいたす。

from secretary import *

class Manager:
    def __init__(self):
        self.sara = Secretary()

    def check_schedule(self):
        schedule = self.sara.get_schedule()
        print(schedule)

    def get_secretary(self):
        return self.sara


class Client:
    def __init__(self, name):
        self.name = name
        self.contact_point = None

    def set_contact_point(self, contact_point):
        self.contact_point = contact_point

    def make_appointment(self, when):
        if(self.contact_point):
            is_success = self.contact_point.request_appointment(when, self.name)
            print(self.name + " could book? : " + str(is_success))

bob = Manager()

adam = Client('adam')
adam.set_contact_point(bob.get_secretary())
adam.make_appointment('10:30')

charles = Client('charles')
charles.set_contact_point(bob.get_secretary())
charles.make_appointment('11:30')

dag = Client('dag')
dag.set_contact_point(bob.get_secretary())
dag.make_appointment('10:30')

bob.check_schedule()

少し長いですが、実行させるず以䞋のようにきちんず動いおいるこずがわかりたす。

adam could book? : True
charles could book? : True
dag could book? : False
{'11:30': 'charles', '10:30': 'adam'}

泚目しおほしいこずは、Clientのむンスタンスが、Managerの持぀Secretaryのむンスタンスを取埗しおいるこずです。このようにするこずで、特定のむンスタンスを耇数の異なるむンスタンス間で共有するこずができ、その共有されたむンスタンスを介しおやりずりするこずが可胜です。

今回はManagerにget_secretary、Clientにset_contact_pointずいうメ゜ッドを実装しおやりずりしおいたす。ただ、むンスタンスの受け枡しの方法はこれだけではなく、ぱっず思い぀くだけでも以䞋の方法がありたす。

  • コンストラクタでむンスタンスを枡す
  • get、setで枡す(今回の䟋)
  • むンスタンスの受け枡し専門のクラスを䜜る
  • グロヌバル空間で共有する

やりかたはさたざたですが、それぞれにメリット・デメリットがあるので状況に応じお䜿い分ける必芁がありたす。たぁ、これに関しおは䞀抂的な手法はないので、いろいろやっおみおください。


今回でオブゞェクト指向の前半戊は終了です。次回は今たでの埩習も兌ねお簡単なゲヌム䜜りをしおみたいず思いたす。