2010年12月22日水曜日

そういえばブログに書いてなかったけど

iPhoneアプリ作りました。
http://itunes.apple.com/jp/app/id407479350

2010年12月15日水曜日

subversion-controlledなXcode Projectの新規作成手順

何回やっても忘れるのでもう備忘録を付けます。
(Xcodeと銘打ってますが手順そのものは汎用的だと思います。どちらかと言うとsubversionの備忘録です)
抜けとかがあったら指摘お願いします。

前提
  • 既存 svn リポジトリ: http://your.svnhost/
  • プロジェクトのURL: http://your.svnhost/path/to/project/NewProject/
  • プロジェクトの場所: ~/work/NewProject/
  • trunk/tag/brunches などはよく分からないので使わない

手順
途中でエラーが出たら最初からやり直すのが無難。
  1. http://your.svnhost/ をXcodeのSCMリポジトリに登録していない場合は登録する
    Setting up your XCode SCM Repositories
  2. Xcode起動→プロジェクト新規作成→テンプレート選択
  3. 場所: ~/work/ プロジェクト名: NewProject → OK
  4. Xcodeを終了
  5. Finderで~/work/に行き、NewProjectフォルダをデスクトップに移動
  6. ターミナルを起動
  7. $ svn mkdir -m "Making a new directory for NewProject." http://your.svnhost/path/to/project/NewProject/
  8. $ cd ~/work/
  9. $ mkdir NewProject
  10. $ svn co http://your.svnhost/path/to/project/NewProject/ NewProject
  11. $ cd NewProject
  12. $ cp -r ~/Desktop/NewProject/* .
  13. $ vi .svnignore
  14. i → 以下をコピペ → :wq
    .DS_Store
    build
    *.pbxuser
    *.perspectiv*
  15. $ svn propset svn:ignore -R -F .svnignore .
  16. $ svn add --force .
  17. $ svn st
    buildフォルダやNewProject.xcodeproj以下にproject.pbxproj以外が無いことを確認
  18. XcodeでメニューのSCM→このプロジェクトのSCMを設定
  19. 右上あたりのSCMルート設定をクリック
  20. Noneをクリック→リポジトリ選択(Recommendedになっているはず、なってなかったら手順1が抜けてるか、そうでなければやり直す)
  21. メニューのSCM→プロジェクト全体をリフレッシュ (一応)
  22. メニューのSCM→プロジェクト全体をコミット
  23. メニューのSCM→SCM Results→何も表示されていないことを確認
  24. デスクトップのNewProjectフォルダを捨てる
以上です。以降はコマンドラインでsubversion管理擦る必要はないでしょう。

参考

この後は

2010年12月8日水曜日

第21章 Proxyパターン : 必要になってから作る



Proxyパターンもそんなに難しくないですね。RealSubject役がProxy役を意識しないところが味噌ですね。


  • Virtual Proxy ... 今回の例
  • Remote Proxy ... ネットワーク越しのメソッド呼び出しに
  • Access Proxy ... メソッド呼び出しにアクセス制限を設けたい時に




timeモジュールのsleep(sec)はスレッドをブロックするので、マルチスレッド化しなければsynchronizeを意識する必要はなさそうです。

実行結果
名前は現在Aliceです。
名前は現在Bobです。
Printerのインスタンス(Bob)を生成中
生成完了
=== Bob ===
Hello, world

ソースコード
#!/usr/bin/env python
# -*- coding: utf8 -*-

# Subject interface
class __Printable:
    def __init__(self): raise NotImplementedError
    def setPrinterName(self, n): raise NotImplementedError
    def getPrinterName(self): raise NotImplementedError
    def printIt(self): raise NotImplementedError

from time import sleep
# RealSubject class
class Printer(__Printable):
    def __init__(self, name):
        self.__name = name
        print "Printerのインスタンス(%s)を生成中" % name
        self.__heavyjob()

    def getPrinterName(self):
        return self.__name
    def setPrinterName(self, n):
        self.__name = n

    def printIt(self, msg):
        print "=== %s ===" % self.__name
        print msg

    def __heavyjob(self):
        sleep(3)
        print '生成完了'

# Proxy class
class PrinterProxy(__Printable):
    def __init__(self, name):
        self.__name = name
        self.__real = None

    def getPrinterName(self):
        return self.__name
    def setPrinterName(self, n):
        self.__name = n
        if not self.__real == None:
            self.__real.setPrinterName(n)

    def printIt(self, msg):
        if self.__real == None:
            self.__realize()
        self.__real.printIt(msg)


    def __realize(self):
        self.__real = Printer(self.__name)

# Client
def main():
    m_printer = PrinterProxy("Alice")
    print "名前は現在%sです。" % m_printer.getPrinterName()
    m_printer.setPrinterName("Bob")
    print "名前は現在%sです。" % m_printer.getPrinterName()
    m_printer.printIt("Hello, world")

if __name__=='__main__': main()

2010年12月6日月曜日

第20章 Flyweightパターン : 同じものを共有して無駄をなくす



今回のFlyweightパターンは分かりやすいですね。
factoryにpool作っておいて、同じものを表すオブジェクトはpoolに入っているものを共有するだけです。

注意点は2つ。詳細は省きます。

  1. 共有しているものを変更すると、複数箇所に影響が及ぶ
  2. 管理されているインスタンスは、ガベージコレクションされない



今回もオリジナルで。とは言っても『元の文字に空白を一文字追加して新しい文字列とする』だけなので全く大したことないですが。
あとpythonにsynchronizedが無くてちょっとがっかり。threading.Queueとか使うとよさそうです。

実行結果
L o r e m   i p s u m   d o l o r   s i t   a m e t ,   c o n s e c t e t u r   a d i p i s c i n g   e l i t .   P r a e s e n t   d i g n i s s i m   d i g n i s s i m   e r o s ,   a c   c o n d i m e n t u m   o r c i   i n t e r d u m   s e d .   U t   e t   d o l o r   a t   e s t   e l e m e n t u m   p o s u e r e .   M a u r i s   i a c u l i s   e u i s m o d   l a c u s ,   e g e t   d a p i b u s   a u g u e   i n t e r d u m   v i t a e .   D o n e c   i d   c o n g u e   e l i t .   D o n e c   e r o s   d i a m ,   c o n s e c t e t u r   s i t   a m e t   v a r i u s   i d ,   f e r m e n t u m   e g e t   n i s i .   V i v a m u s   l a o r e e t   a u c t o r   m a s s a   e g e t   p o r t t i t o r .   C r a s   a   e s t   a   n i s i   e g e s t a s   s a g i t t i s   e t   s i t   a m e t   p u r u s .   F u s c e   n e c   u l l a m c o r p e r   p u r u s .   N u l l a   f a c i l i s i .   V e s t i b u l u m   i n   o r c i   s e d   n i b h   r u t r u m   v e n e n a t i s . 
 
 P r a e s e n t   f a c i l i s i s ,   n i s l   t i n c i d u n t   m a l e s u a d a   s u s c i p i t ,   d o l o r   u r n a   f a c i l i s i s   d o l o r ,   v i t a e   i a c u l i s   l o r e m   l i b e r o   s e d   r i s u s .   Q u i s q u e   e t   p l a c e r a t   m a u r i s .   I n   e g e t   a d i p i s c i n g   n i b h .   P e l l e n t e s q u e   i m p e r d i e t   u l t r i c i e s   f e l i s ,   q u i s   p o r t t i t o r   a u g u e   v i v e r r a   a t .   C u r a b i t u r   s e d   n i b h   i p s u m .   E t i a m   c o n d i m e n t u m   l e c t u s   i d   m a u r i s   l a o r e e t   e g e t   r u t r u m   n u n c   r h o n c u s .   F u s c e   e g e s t a s   s a g i t t i s   l a c u s ,   s e d   u l l a m c o r p e r   s a p i e n   m o l e s t i e   a .   M a u r i s   o r n a r e   t u r p i s   u t   l i g u l a   f a u c i b u s   e t   p o r t a   e s t   l a o r e e t .   U t   e u   p u r u s   a c   d i a m   v a r i u s   f a u c i b u s   e l e m e n t u m   v i t a e   a u g u e .   N u n c   i d   o d i o   v e l i t ,   v e l   r h o n c u s   n u n c .   S e d   e g e s t a s   t r i s t i q u e   s o l l i c i t u d i n .   I n   v i v e r r a   m a u r i s   a t   i p s u m   v u l p u t a t e   m o l l i s .   D o n e c   l e c t u s   f e l i s ,   r h o n c u s   v e l   e l e i f e n d   s e d ,   f a c i l i s i s   a c c u m s a n   l e c t u s .   P r a e s e n t   n e c   m a g n a   v e l   e s t   g r a v i d a   p r e t i u m   s a g i t t i s   v i t a e   d u i .   S u s p e n d i s s e   p o t e n t i .   C u r a b i t u r   l o b o r t i s   t o r t o r   n e c   e s t   a d i p i s c i n g   l a c i n i a . 
 
 L o r e m   i p s u m   d o l o r   s i t   a m e t ,   c o n s e c t e t u r   a d i p i s c i n g   e l i t .   P e l l e n t e s q u e   f e u g i a t   a u g u e   v e l   m a s s a   v o l u t p a t   f r i n g i l l a .   N u l l a m   m a t t i s   a c c u m s a n   s a p i e n   e t   d i c t u m .   S u s p e n d i s s e   n e c   u r n a   e g e t   n i s i   p e l l e n t e s q u e   s e m p e r   s i t   a m e t   q u i s   v e l i t .   A e n e a n   a c c u m s a n   d i a m   i n   s e m   c o n s e c t e t u r   i d   u l t r i c i e s   d o l o r   l o b o r t i s .   I n   e g e s t a s   s e m   u t   n u n c   c o n v a l l i s   s o d a l e s   i n   u t   t o r t o r .   P r a e s e n t   n e c   m a g n a   n e q u e .   P r a e s e n t   s e m p e r   s a g i t t i s   d o l o r   a   d a p i b u s .   N a m   h e n d r e r i t ,   l o r e m   n e c   f a u c i b u s   v e h i c u l a ,   n u n c   e n i m   i n t e r d u m   s e m ,   n o n   e g e s t a s   s a p i e n   a n t e   e u   l e c t u s .   P e l l e n t e s q u e   h a b i t a n t   m o r b i   t r i s t i q u e   s e n e c t u s   e t   n e t u s   e t   m a l e s u a d a   f a m e s   a c   t u r p i s   e g e s t a s .   D u i s   v e l   m a u r i s   t e l l u s ,   e g e t   p l a c e r a t   m i .   P e l l e n t e s q u e   m e t u s   m i ,   e l e i f e n d   u t   t e m p u s   i n ,   f e r m e n t u m   q u i s   l i g u l a .   A l i q u a m   e r a t   l e o ,   m o l l i s   a c   c u r s u s   a c ,   f e u g i a t   i n   n i s i .   E t i a m   g r a v i d a   e u i s m o d   l e c t u s   e t   c o n s e c t e t u r .   E t i a m   i a c u l i s   e l e m e n t u m   r i s u s ,   i n   e l e i f e n d   v e l i t   a u c t o r   a c .   V i v a m u s   u l l a m c o r p e r ,   l o r e m   n o n   m o l e s t i e   c o n v a l l i s ,   t e l l u s   a n t e   p u l v i n a r   s a p i e n ,   v e l   p o r t t i t o r   m a s s a   o d i o   e u   l e c t u s .   D o n e c   a c   r i s u s   t r i s t i q u e   n i s l   e u i s m o d   c o n s e c t e t u r .   D o n e c   m o l l i s   s e m p e r   a n t e ,   q u i s   f e u g i a t   l a c u s   i m p e r d i e t   a t .   N a m   e u   l o r e m   s i t   a m e t   v e l i t   s a g i t t i s   c o n s e c t e t u r   a   v o l u t p a t   m i .   S u s p e n d i s s e   s e d   a d i p i s c i n g   l e c t u s . 
 
 C l a s s   a p t e n t   t a c i t i   s o c i o s q u   a d   l i t o r a   t o r q u e n t   p e r   c o n u b i a   n o s t r a ,   p e r   i n c e p t o s   h i m e n a e o s .   N a m   c o n v a l l i s   f e r m e n t u m   e n i m ,   v e l   b l a n d i t   n i s l   b l a n d i t   v e l .   I n t e g e r   a c   j u s t o   n u l l a .   N u l l a   e r o s   e l i t ,   u l l a m c o r p e r   s e d   c o n d i m e n t u m   v e l ,   p o r t a   s e d   e n i m .   S e d   a t   l i b e r o   v e l   l a c u s   p r e t i u m   d i c t u m .   M a u r i s   h e n d r e r i t   u r n a   s i t   a m e t   m i   r h o n c u s   n o n   a l i q u a m   e n i m   i m p e r d i e t .   C u r a b i t u r   a   e r o s   a   d i a m   u l l a m c o r p e r   r h o n c u s .   P r o i n   e t   m a g n a   i n   n i s i   e l e m e n t u m   d i c t u m   v e l   s i t   a m e t   l e c t u s .   U t   t r i s t i q u e   d u i   q u i s   v e l i t   a u c t o r   m o l e s t i e   d a p i b u s   n u l l a   v a r i u s .   F u s c e   v i t a e   v e l i t   p e l l e n t e s q u e   m a g n a   c o n s e q u a t   p l a c e r a t .   M a e c e n a s   d i g n i s s i m   r i s u s   n o n   e s t   s o l l i c i t u d i n   v u l p u t a t e .   M a u r i s   v i t a e   o r c i   f r i n g i l l a   n u l l a   p r e t i u m   p h a r e t r a .   C u r a b i t u r   a c   f a c i l i s i s   t o r t o r .   C u r a b i t u r   e g e t   m o l l i s   v e l i t .   I n   a c   l e c t u s   e l i t ,   a c   p o s u e r e   r i s u s .   Q u i s q u e   q u i s   l i g u l a   m e t u s ,   u t   b l a n d i t   l i b e r o .   C u r a b i t u r   s e d   n i b h   v i t a e   m i   e l e m e n t u m   m o l e s t i e .   D o n e c   e u i s m o d   a r c u   u t   t u r p i s   u l t r i c e s   v i v e r r a .   N u l l a   f a c i l i s i . 
 
 N u l l a   i d   p u l v i n a r   m a u r i s .   N u n c   m a t t i s   f a c i l i s i s   a u g u e ,   s e d   t r i s t i q u e   e s t   i n t e r d u m   s e d .   P r a e s e n t   u l t r i c i e s   n i b h   i d   a u g u e   s u s c i p i t   i d   u l t r i c i e s   m e t u s   a d i p i s c i n g .   A l i q u a m   e r a t   v o l u t p a t .   V e s t i b u l u m   u t   i p s u m   i n   u r n a   l o b o r t i s   f e u g i a t   v i t a e   s i t   a m e t   a r c u .   A l i q u a m   d i a m   p u r u s ,   p r e t i u m   n e c   c o n s e q u a t   v e l ,   p l a c e r a t   a   n i b h .   A e n e a n   l e o   n i s l ,   u l l a m c o r p e r   i n   p l a c e r a t   c o n g u e ,   m o l e s t i e   u l t r i c e s   t o r t o r .   P r a e s e n t   d i a m   l i g u l a ,   e g e s t a s   a   f e r m e n t u m   s e d ,   u l t r i c i e s   i d   o d i o .   D o n e c   a t   p u r u s   i p s u m .   P h a s e l l u s   p o r t a   p l a c e r a t   s c e l e r i s q u e .   S u s p e n d i s s e   h e n d r e r i t   r u t r u m   q u a m   u t   p o r t t i t o r . 

ソースコード
#!/usr/bin/env python
# -*- coding: utf8 -*-

import sys

# Flyweight class
class SparseChar:
    def __init__(self, char):
        self.__char = char[0]

    def draw(self):
        sys.stdout.write(self.__char+" ")

# FlyweightFactory class
class SparseCharFactory:
    def __init__(self):
        self.__pool = {}

    def getSparseChar(self, char):
        if not self.__pool.has_key(char):
            self.__pool[char] = SparseChar(char)
        return self.__pool[char]

# Client class
class SparseString:
    def __init__(self, string):
        self.__string = string
        self.factory = SparseCharFactory()

    def draw(self):
        for s in self.__string:
            self.factory.getSparseChar(s).draw()
        print ""


def main():
    m_sparseString = SparseString("""Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent dignissim dignissim eros, ac condimentum orci interdum sed. Ut et dolor at est elementum posuere. Mauris iaculis euismod lacus, eget dapibus augue interdum vitae. Donec id congue elit. Donec eros diam, consectetur sit amet varius id, fermentum eget nisi. Vivamus laoreet auctor massa eget porttitor. Cras a est a nisi egestas sagittis et sit amet purus. Fusce nec ullamcorper purus. Nulla facilisi. Vestibulum in orci sed nibh rutrum venenatis.

Praesent facilisis, nisl tincidunt malesuada suscipit, dolor urna facilisis dolor, vitae iaculis lorem libero sed risus. Quisque et placerat mauris. In eget adipiscing nibh. Pellentesque imperdiet ultricies felis, quis porttitor augue viverra at. Curabitur sed nibh ipsum. Etiam condimentum lectus id mauris laoreet eget rutrum nunc rhoncus. Fusce egestas sagittis lacus, sed ullamcorper sapien molestie a. Mauris ornare turpis ut ligula faucibus et porta est laoreet. Ut eu purus ac diam varius faucibus elementum vitae augue. Nunc id odio velit, vel rhoncus nunc. Sed egestas tristique sollicitudin. In viverra mauris at ipsum vulputate mollis. Donec lectus felis, rhoncus vel eleifend sed, facilisis accumsan lectus. Praesent nec magna vel est gravida pretium sagittis vitae dui. Suspendisse potenti. Curabitur lobortis tortor nec est adipiscing lacinia.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque feugiat augue vel massa volutpat fringilla. Nullam mattis accumsan sapien et dictum. Suspendisse nec urna eget nisi pellentesque semper sit amet quis velit. Aenean accumsan diam in sem consectetur id ultricies dolor lobortis. In egestas sem ut nunc convallis sodales in ut tortor. Praesent nec magna neque. Praesent semper sagittis dolor a dapibus. Nam hendrerit, lorem nec faucibus vehicula, nunc enim interdum sem, non egestas sapien ante eu lectus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis vel mauris tellus, eget placerat mi. Pellentesque metus mi, eleifend ut tempus in, fermentum quis ligula. Aliquam erat leo, mollis ac cursus ac, feugiat in nisi. Etiam gravida euismod lectus et consectetur. Etiam iaculis elementum risus, in eleifend velit auctor ac. Vivamus ullamcorper, lorem non molestie convallis, tellus ante pulvinar sapien, vel porttitor massa odio eu lectus. Donec ac risus tristique nisl euismod consectetur. Donec mollis semper ante, quis feugiat lacus imperdiet at. Nam eu lorem sit amet velit sagittis consectetur a volutpat mi. Suspendisse sed adipiscing lectus.

Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam convallis fermentum enim, vel blandit nisl blandit vel. Integer ac justo nulla. Nulla eros elit, ullamcorper sed condimentum vel, porta sed enim. Sed at libero vel lacus pretium dictum. Mauris hendrerit urna sit amet mi rhoncus non aliquam enim imperdiet. Curabitur a eros a diam ullamcorper rhoncus. Proin et magna in nisi elementum dictum vel sit amet lectus. Ut tristique dui quis velit auctor molestie dapibus nulla varius. Fusce vitae velit pellentesque magna consequat placerat. Maecenas dignissim risus non est sollicitudin vulputate. Mauris vitae orci fringilla nulla pretium pharetra. Curabitur ac facilisis tortor. Curabitur eget mollis velit. In ac lectus elit, ac posuere risus. Quisque quis ligula metus, ut blandit libero. Curabitur sed nibh vitae mi elementum molestie. Donec euismod arcu ut turpis ultrices viverra. Nulla facilisi.

Nulla id pulvinar mauris. Nunc mattis facilisis augue, sed tristique est interdum sed. Praesent ultricies nibh id augue suscipit id ultricies metus adipiscing. Aliquam erat volutpat. Vestibulum ut ipsum in urna lobortis feugiat vitae sit amet arcu. Aliquam diam purus, pretium nec consequat vel, placerat a nibh. Aenean leo nisl, ullamcorper in placerat congue, molestie ultrices tortor. Praesent diam ligula, egestas a fermentum sed, ultricies id odio. Donec at purus ipsum. Phasellus porta placerat scelerisque. Suspendisse hendrerit rutrum quam ut porttitor.""")
    m_sparseString.draw()


if __name__=='__main__': main()

第19章 Stateパターン : 状態をクラスとして表現する



今回はStateパターン。なかなかエキセントリックなデザインパターンです。

要は
  • メソッドの実装をある状態に応じて切り替える(条件分岐させる)
ような実装を
  • ある状態というクラスがメソッドの実装を記述する
という、説明しても『???』なデザインパターンです。

もう少し概念的な説明すると、

  • 普通の条件分岐…『この状態ならこういう処理にする』
  • Stateパターン…『この状態こういう処理をする』

というように、状態が主語になるイメージです。


いつ使うのか。メリットは何か。

使う場面を間違えると却って混乱しそうなStateパターン。自分で例を考えて実装してみて、有効だと感じた点を列挙します。

  1. どのメソッドにも同じような条件分岐が散らばるほどに重要な『状態』がある時
    まあこれは当たり前っちゃあ当たり前。『この状態の時に呼ばれたらこう動く』という説明のメソッドがあったら要チェック。

  2. 将来状態が増減しそうな時
    これも当たり前かな

  3. 状態を矛盾なく取り扱える
    本書でも触れてますが、このパターンを適用する時は各Stateは排反である必要があるので、必竟自己矛盾が起きません
    これはプログラミングする上でとても大事です。『このifはelse ifにしないと動かない』みたいな分かりづらさから解放されます。
逆にデメリットは以下のような点。
  1. 状態が増えすぎるとしんどい
    特にベクトルの違う複数の状態の組み合わせだと、状態数が指数的に増えるおそれがある




今回は完全オリジナル。どこかのブログのパクリで、『マリオでStateパターン』です。
1ターンにランダムで

  • きのこを食べる
  • フラワーを食べる
  • スターを食べる(スターは3ターンで切れる)
  • 敵に当たる
  • 穴に落ちる

のなかから一つの行動をします。State(状態)は

  • チビマリオ
  • デカマリオ
  • フラワーマリオ
  • 上記3種のスター状態

の計6種類です。
これだと、例えば『しっぽマリオを追加する』なんて時にSippoMarioStateクラスとStarSippoMarioStateクラスを追加するだけで実現できてしまいます(他所はいじる必要なし!)。Stateパターンの有効なケースですね。


実行結果
[残5:チビマリオ] スターを食べた!  チビマリオ★ になった!
[残5:チビマリオ★] ...
[残5:チビマリオ★] キノコを食べた!  デカマリオ★ になった!
[残5:デカマリオ★] スターが切れた!  デカマリオ になった!
[残5:デカマリオ] スターを食べた!  デカマリオ★ になった!
[残5:デカマリオ★] 敵に接触!  敵を倒した!
[残5:デカマリオ★] スターを食べた!  デカマリオ★ になった!
[残5:デカマリオ★] 穴に落ちた!  死んだ! 残り残機:4
[残4:チビマリオ] キノコを食べた!  デカマリオ になった!
[残4:デカマリオ] ...
[残4:デカマリオ] 穴に落ちた!  死んだ! 残り残機:3
[残3:チビマリオ] ...
[残3:チビマリオ] フラワーを食べた!  デカマリオ になった!
[残3:デカマリオ] スターを食べた!  デカマリオ★ になった!
[残3:デカマリオ★] フラワーを食べた!  ファイアーマリオ★ になった!
[残3:ファイアーマリオ★] フラワーを食べた!  意味はない!
[残3:ファイアーマリオ★] スターが切れた!  ファイアーマリオ になった!
[残3:ファイアーマリオ] ...
[残3:ファイアーマリオ] 穴に落ちた!  死んだ! 残り残機:2
[残2:チビマリオ] フラワーを食べた!  デカマリオ になった!
[残2:デカマリオ] 敵に接触!  チビマリオ になった!
[残2:チビマリオ] 穴に落ちた!  死んだ! 残り残機:1
[残1:チビマリオ] キノコを食べた!  デカマリオ になった!
[残1:デカマリオ] スターを食べた!  デカマリオ★ になった!
[残1:デカマリオ★] フラワーを食べた!  ファイアーマリオ★ になった!
[残1:ファイアーマリオ★] キノコを食べた!  意味はない!
[残1:ファイアーマリオ★] スターが切れた!  ファイアーマリオ になった!
[残1:ファイアーマリオ] フラワーを食べた!  意味はない!
[残1:ファイアーマリオ] 敵に接触!  チビマリオ になった!
[残1:チビマリオ] フラワーを食べた!  デカマリオ になった!
[残1:デカマリオ] 敵に接触!  チビマリオ になった!
[残1:チビマリオ] 敵に接触!  死んだ! 残り残機:0
[残0:チビマリオ] 穴に落ちた!  GAME OVER
30ターンプレイしました

ソースコード
#!/usr/bin/env python
# -*- coding: utf8 -*-

class __MarioState:
    def __init__(self): raise NotImplementedError
    def doGetKinoko(self, context): raise NotImplementedError # キノコ取得
    def doGetFlower(self, context): raise NotImplementedError # フラワー取得
    def doGetStar(self, context): raise NotImplementedError # スター取得
    def doLostStar(self, context): raise NotImplementedError # スター消滅
    def doEncounter(self, context): raise NotImplementedError # 敵に接触
    def doFall(self, context): raise NotImplementedError # 穴に落ちる
    def __str__(self): raise NotImplementedError


class NormalMario(__MarioState):
    def __init__(self): pass
    def doGetKinoko(self, context): context.changeState(BigMario())
    def doGetFlower(self, context): context.changeState(BigMario())
    def doEncounter(self, context): context.dead()
    def doGetStar(self, context): context.changeState(StarNormalMario())
    def doLostStar(self, context): print "bug"
    def doFall(self, context): context.dead()
    def __str__(self): return "チビマリオ"

class BigMario(__MarioState):
    def __init__(self): pass
    def doGetKinoko(self, context): context.nope()
    def doGetFlower(self, context): context.changeState(FlowerMario())
    def doEncounter(self, context): context.changeState(NormalMario())
    def doGetStar(self, context): context.changeState(StarBigMario())
    def doLostStar(self, context): print "bug"
    def doFall(self, context): context.dead()
    def __str__(self): return "デカマリオ"

class FlowerMario(__MarioState):
    def __init__(self): pass
    def doGetKinoko(self, context): context.nope()
    def doGetFlower(self, context): context.nope()
    def doEncounter(self, context): context.changeState(NormalMario())
    def doGetStar(self, context): context.changeState(StarFlowerMario())
    def doLostStar(self, context): print "bug"
    def doFall(self, context): context.dead()
    def __str__(self): return "ファイアーマリオ"

class StarNormalMario(__MarioState):
    def __init__(self): pass
    def doGetKinoko(self, context): context.changeState(StarBigMario())
    def doGetFlower(self, context): context.changeState(StarBigMario())
    def doGetStar(self, context): context.changeState(StarNormalMario())
    def doLostStar(self, context): context.changeState(NormalMario())
    def doEncounter(self, context): context.defeat()
    def doFall(self, context): context.dead()
    def __str__(self): return "チビマリオ★"

class StarBigMario(__MarioState):
    def __init__(self): pass
    def doGetKinoko(self, context): context.nope()
    def doGetFlower(self, context): context.changeState(StarFlowerMario())
    def doGetStar(self, context): context.changeState(StarBigMario())
    def doLostStar(self, context): context.changeState(BigMario())
    def doEncounter(self, context): context.defeat()
    def doFall(self, context): context.dead()
    def __str__(self): return "デカマリオ★"

class StarFlowerMario(__MarioState):
    def __init__(self): pass
    def doGetKinoko(self, context): context.nope()
    def doGetFlower(self, context): context.nope()
    def doGetStar(self, context): context.changeState(StarFlowerMario())
    def doLostStar(self, context): context.changeState(FlowerMario())
    def doEncounter(self, context): context.defeat()
    def doFall(self, context): context.dead()
    def __str__(self): return "ファイアーマリオ★"



class __Context:
    def __init__(self): raise NotImplementedError
    def changeState(self, mariostate): raise NotImplementedError # 状態が変化した
    def defeat(self): raise NotImplementedError # 敵を倒した
    def dead(self): raise NotImplementedError # 死んだ
    def nope(self): raise NotImplementedError # 何も起きない

import sys
from threading import Timer
class Game(__Context):
    def __init__(self):
        self.state = NormalMario()
        self.zanki = 5 # 残機
        self.turncount = 0 # 何ターン目か
        self.starcount = 99999 # スター用カウンタ

    def changeState(self, mariostate):
        self.state = mariostate
        print str(mariostate)+" になった!"

    def defeat(self):
        print "敵を倒した!"

    def dead(self):
        self.zanki -= 1
        if self.zanki < 0:
            print "GAME OVER"
            print "%dターンプレイしました" % self.turncount
            sys.exit(0)
        print "死んだ! 残り残機:%d" % self.zanki
        self.state = NormalMario()
        self.starcount = 99999

    def nope(self):
        print "意味はない!"

    #--

    def youGetKinoko(self):
        print "[残%d:%s] キノコを食べた! " % (self.zanki, self.state),
        self.state.doGetKinoko(self)
    def youGetFlower(self):
        print "[残%d:%s] フラワーを食べた! " % (self.zanki, self.state),
        self.state.doGetFlower(self)
    def youEncounter(self):
        print "[残%d:%s] 敵に接触! " % (self.zanki, self.state),
        self.state.doEncounter(self)
    def youFall(self):
        print "[残%d:%s] 穴に落ちた! " % (self.zanki, self.state),
        self.state.doFall(self)
    def nothingHappened(self):
        print "[残%d:%s] ..." % (self.zanki, self.state)

    def youGetStar(self):
        print "[残%d:%s] スターを食べた! " % (self.zanki, self.state),
        self.state.doGetStar(self)
        self.starcount = 0
    def __youLostStar(self):
        print "[残%d:%s] スターが切れた! " % (self.zanki, self.state),
        self.state.doLostStar(self)

    def youCountTurn(self):
        self.turncount += 1
        self.starcount += 1
        if self.starcount == 3:
            self.__youLostStar()



from time import sleep
from random import randint
def main():
    m_game = Game()
    while True:
        # random action
        m_game.youCountTurn()
        act = randint(1,6)
        if   act==1: m_game.youGetKinoko()
        elif act==2: m_game.youGetFlower()
        elif act==3: m_game.youGetStar()
        elif act==4: m_game.youEncounter()
        elif act==5: m_game.youFall()
        elif act==6: m_game.nothingHappened()
        sleep(0.1)

if __name__=='__main__': main()

第18章 Memento : 状態を保存する



今回はMementoパターン。undo/redo/snapshotといった機能をカプセル化を破壊することなく提供する方法です。

  • Originator役はMemento役のオブジェクトを生成し、
  • Caretaker役はMemento役のオブジェクトを保持します。

オブジェクトの状態を保存する』という機能は、undo以外にも用途がありそうです。

--

簡単のため、所持金のみ増減するゲームにしてあります。

実行結果
4の目が出た!... 
2の目が出た!... 所持金が半分になった!
1の目が出た!... 所持金が100増えた!
所持金が増えたので状態を保存。現在150円
5の目が出た!... 
3の目が出た!... 
5の目が出た!... 
2の目が出た!... 所持金が半分になった!
2の目が出た!... 所持金が半分になった!
所持金がかなり減ったので状態を復元。現在150円
(中略)
6の目が出た!... 
2の目が出た!... 所持金が半分になった!
所持金がかなり減ったので状態を復元。現在1412円
5の目が出た!... 
2の目が出た!... 所持金が半分になった!
2の目が出た!... 所持金が半分になった!
所持金がかなり減ったので状態を復元。現在1412円
1の目が出た!... 所持金が100増えた!
所持金が増えたので状態を保存。現在1512円
6の目が出た!... 
終了。最終金額1512円

ソースコード
game.py
# -*- coding: utf8 -*-

from random import randint
class Gamer:
    def __init__(self, money):
        self.__money = money

    def getMoney(self): return self.__money
    def bet(self):
        dice = randint(1,6)
        print "%dの目が出た!..." % (dice),
        if dice==1:
            self.__money += 100
            print "所持金が100増えた!"
        elif dice==2:
            self.__money /= 2
            print "所持金が半分になった!"
        else:
            print ""

    def createMemento(self):
        return Memento(self.__money)

    def restoreMemento(self, memento):
        self.__money = memento.getMoney()



class Memento:
    # protected constructor
    def __init__(self, money):
        self.__money = money

    def getMoney(self): return self.__money

main.py
#!/usr/bin/env python
# -*- coding: utf8 -*-

from game import Gamer, Memento
from time import sleep
def main():
    gamer = Gamer(100)
    memento = gamer.createMemento()
    for i in xrange(100):
        gamer.bet()
        if gamer.getMoney() > memento.getMoney():
            memento = gamer.createMemento()
            print "所持金が増えたので状態を保存。現在%d円" % (gamer.getMoney())
        elif gamer.getMoney() < memento.getMoney() / 2:
            gamer.restoreMemento(memento)
            print "所持金がかなり減ったので状態を復元。現在%d円" % (gamer.getMoney())
        sleep(0.5)

    print "終了。最終金額%d円" % (gamer.getMoney())

if __name__=='__main__': main()

2010年11月28日日曜日

第17章 Observerパターン : 状態の変化を通知する



今回はObserverパターン。とは言っても(本文中にもありますが)『観察』というよりは『通知』、Publish―Subscribeの関係になってます。そして相変わらず委譲を使ってます。

これも前回同様、Subject役をモデル、Observer役をコントローラまたはビューに当てはめるとMVCの典型的実装パターンですね。と思ってたら本でもちょこっとこのトピックに触れていました。

『更新のためのヒント情報の扱い』の節は私にとっていいヒントになりました。変化する情報に応じて一個ずつupdateメソッドを作るのでなく、メソッドはupdate一つにし、変化する情報を引数に入れるという方法は新鮮でした。

実行結果
1
*
4
****
0

5
*****
4
****
10
**********
3
***
・
・
・

ソースコード
#!/usr/bin/env python
# -*- coding: utf8 -*-

class ConstructInterfaceError(Exception):
    def __init__(self):
        pass

# Subject
class NumberGenerator:
    _observers = []
    def __init__(self): raise Exception("Abstract class")
    def addObserver(self, o):
        self._observers.append(o)

    def deleteObserver(self, o):
        self._observers.remove(o)

    def notifyObservers(self):
        for o in self._observers:
            o.update(self)

    def getNumber(self): raise NotImplementedError
    def execute(self): raise NotImplementedError

# ConcreteSubject
from random import randint
class RandomNumberGenerator(NumberGenerator):
    def __init__(self, maxnumber):
        self.__maxnumber = maxnumber
        self.__number = 0

    def getNumber(self):
        return self.__number

    def execute(self):
        self.__number = randint(0,self.__maxnumber)
        self.notifyObservers()



# Observer interface
class __Observer:
    def __init__(self): raise ConstructInterfaceError
    def update(self, subject): raise NotImplementedError

# ConcreteObserver
from time import sleep
class DigitObserver(__Observer):
    def __init__(self): pass
    def update(self, subject):
        print subject.getNumber()
        sleep(0.5)

# ConcreteObserver
class GraphObserver(__Observer):
    def __init__(self): pass
    def update(self, subject):
        print ''.join(['*' for i in xrange(subject.getNumber())])
        sleep(0.5)



def main():
    # NumberGenerator's instance
    g = RandomNumberGenerator(10)

    # Observer's instance
    do = DigitObserver()
    go = GraphObserver()

    g.addObserver(do)
    g.addObserver(go)
    for i in xrange(50):
        g.execute()

if __name__=='__main__': main()

2010年11月27日土曜日

第16章 Mediatorパターン : 相手は相談役1人だけ



今回はMediatorパターン。Mediatorは仲介者で、オブジェクト間の連携を一手に担ってくれます。
下例のようなGUIプログラミングによく使われるそうです。MVCモデルに通じますね。



今回はGUIが絡んでいるので、JavaScriptで書いてみました。
JavaScriptはほぼ初挑戦なので、変な実装とかあったら指摘もらえると助かります。
(JavaScriptのOOPってこんなにしんどいんですか?)


実行結果


GuestLogin
Username: 
Password: 


ソースコード
// id="my_content" のelementがあると仮定している

function onLoad(){
    var container = document.getElementById("my_content");
    var loginform = new LoginForm();
    loginform.appendInto(container);
}


// class declaration
function ColleagueInputButton(){ // implements Colleague Interface
    this.initialize.apply(this, arguments);
}
ColleagueInputButton.prototype = {
     initialize:
        function(caption){
            this.content = document.createElement('input');
            this.content.setAttribute('type', 'button');
            this.content.setAttribute('value', caption);
        }
    ,appendInto:
        function(parentContainer){
            parentContainer.appendChild(this.content);
        }
    ,setMediator: // Colleague Interface method
        function(mediator){
            this.content.onclick = mediator.onClick;
        }
    ,setColleagueEnabled: // Colleague Interface method
        function(enabled){
            this.content.disabled = !enabled;
        }
};


// class declaration
function ColleagueInputTextField(){ // implements Colleague Interface
    this.initialize.apply(this, arguments);
}
ColleagueInputTextField.prototype = {
     initialize:
        function(text, columns, passwordtype){
            this.content = document.createElement('input');
            if(passwordtype){
                this.content.setAttribute('type', 'password');
            }else{
                this.content.setAttribute('type', 'text');
            }
            this.content.setAttribute('value', text);
            this.content.setAttribute('size', columns);
        }
    ,appendInto:
        function(parentContainer){
            parentContainer.appendChild(this.content);
        }
    ,getText:
        function(){
            return this.content.value;
        }
    ,setText:
        function(text){
            this.content.value = text;
            this.content.onchange();
        }
    ,setMediator: // Colleague Interface method
        function(mediator){
            this.content.onchange = mediator.colleagueChanged;
        }
    ,setColleagueEnabled: // Colleague Interface method
        function(enabled){
            this.content.disabled = !enabled;
            if(enabled){
                this.content.setAttribute('style', 'background-color: #FFF;');
            }else{
                this.content.setAttribute('style', 'background-color: #EEE;');
            }
        }
};


// class declaration
function ColleagueInputRadioButton(){ // implements Colleague Interface
    this.initialize.apply(this, arguments);
}
ColleagueInputRadioButton.prototype = {
     initialize:
        function(caption, groupname, state){
            this.content = document.createElement('input');
            this.content.setAttribute('type', 'radio');
            this.content.setAttribute('name', groupname);
            this.content.defaultChecked = state;
        }
    ,appendInto:
        function(parentContainer){
            parentContainer.appendChild(this.content);
        }
    ,isChecked:
        function(){
            return this.content.checked;
        }
    ,setMediator: // Colleague Interface method
        function(mediator){
            this.content.onchange = mediator.colleagueChanged;
        }
    ,setColleagueEnabled: // Colleague Interface method
        function(enabled){
            this.content.disabled = !enabled;
        }
};


// class declaration
function LoginForm(){ // implements Mediator Interface
    this.radioGuest = null;
    this.radioLogin = null;
    this.textUser = null;
    this.textPass = null;
    this.buttonOK = null;
    this.buttonCancel = null;
    this.initialize.apply(this, arguments);
}
LoginForm.prototype = {
     initialize:
        function(){
            self = this;
            this.content = document.createElement('form');
            this.createColleagues();

            this.radioGuest.appendInto(this.content);
            span = document.createElement('span');
            span.innerHTML = "Guest";
            this.content.appendChild(span);
            this.radioLogin.appendInto(this.content);
            span = document.createElement('span');
            span.innerHTML = "Login";
            this.content.appendChild(span);
            this.content.appendChild(document.createElement('br'));

            span = document.createElement('span');
            span.innerHTML = "Username: ";
            this.content.appendChild(span);
            this.textUser.appendInto(this.content);
            this.content.appendChild(document.createElement('br'));
            span = document.createElement('span');
            span.innerHTML = "Password: ";
            this.content.appendChild(span);
            this.textPass.appendInto(this.content);
            this.content.appendChild(document.createElement('br'));

            this.buttonOK.appendInto(this.content);
            this.buttonCancel.appendInto(this.content);

            this.colleagueChanged();
        }
    ,appendInto:
        function(parentContainer){
            parentContainer.appendChild(this.content);
        }
    ,createColleagues: // Mediator Interface method
        function(){
            this.radioGuest = new ColleagueInputRadioButton("Guest", "usertype", true);
            this.radioLogin = new ColleagueInputRadioButton("Login", "usertype", false);
            this.textUser = new ColleagueInputTextField("", 10);
            this.textPass = new ColleagueInputTextField("", 10, true);
            this.buttonOK = new ColleagueInputButton("OK");
            this.buttonCancel = new ColleagueInputButton("Cancel");
            this.radioGuest.setMediator(this);
            this.radioLogin.setMediator(this);
            this.textUser.setMediator(this);
            this.textPass.setMediator(this);
            this.buttonOK.setMediator(this);
            this.buttonCancel.setMediator(this);
        }
    ,colleagueChanged: // Mediator Interface method
        function(){
            if(self.radioGuest.isChecked() === true){
                self.textUser.setColleagueEnabled(false);
                self.textPass.setColleagueEnabled(false);
                self.buttonOK.setColleagueEnabled(true);
            }else{
                self.textUser.setColleagueEnabled(true);
                // and also needs to check textUser&textPass
                if(self.textUser.getText().length === 0){
                    self.textPass.setColleagueEnabled(false);
                    self.buttonOK.setColleagueEnabled(false);
                }else{
                    self.textPass.setColleagueEnabled(true);
                    if(self.textPass.getText().length === 0){
                        self.buttonOK.setColleagueEnabled(false);
                    }else{
                        self.buttonOK.setColleagueEnabled(true);
                    }
                }
            }
        }
    ,onClick:
        function(){
            if(this.value === "Cancel"){
                self.textUser.setText("");
                self.textPass.setText("");
            }
            else{
                if(self.radioGuest.isChecked() === true){
                    alert("Hello, Guest!");
                }else{
                    alert("Hello, "+self.textUser.getText()+"!\nYour pass is "+self.textPass.getText()+", right??");
                }
            }
        }
};

window.onload = onLoad;

2010年11月26日金曜日

ネットで見かけた素敵なvimrc

imap {} {}<Left>
imap [] []<Left>
imap () ()<Left>
imap <> <><Left>
imap '' ''<Left>
imap "" ""<Left>

発想の勝利ですね。

追記(2010/11/27 18:49)
空のカッコを書くときに異常にウザ買ったので消しました。

第15章 Facadeパターン : シンプルな窓口



Facadeはファサードって読むらしいです。フランス語。
概要とポイントは本文から引用します。
Facadeパターンは、複雑にからみ合ってごちゃごちゃした詳細をまとめ、高レベルのインターフェイス(API)を提供します。(中略)Facade役はシステムの内側にある各クラスの役割や依存関係を考えて、正しい順番でクラスを利用します。
非常に大きなシステムが、多数のクラス・多数のパッケージを抱えるとき、要所要所にFacadeパターンを適用すると、システムはより便利になるでしょう。
はっきりと言葉で表現できるノウハウは、プログラマの頭の中に隠しておくべきものではなく、コードとして表現しておくべきものなのです。
いつも決まった手順でクラスを利用するときは、その手順そのものをクラス(メソッド)にしてしまおうということです。

--

今回は練習問題の、リンク集をつくる方を書きました。
また、色々簡略化しています。

実行結果
linkpage.html
<html><head><title>Link page</title></head><body>
<h1>Link page</h1>
<p><a href="mailto:hyuki@hyuki.com">Hiroshi Yuki</a></p>
<p><a href="mailto:hanako@hyuki.com">Hanako Sato</a></p>
<p><a href="mailto:tomura@hyuki.com">Tomura</a></p>
<p><a href="mailto:mamoru@hyuki.com">Mamoru Takahashi</a></p>
</body></html>

ソースコード
#!/usr/bin/env python
# -*- coding: utf8 -*-

class Database:
    def __init__(self): raise Exception("Do not construct instances")
    @classmethod
    def getProperties(cls):
        return [
                 ("hyuki@hyuki.com","Hiroshi Yuki")
                ,("hanako@hyuki.com","Hanako Sato")
                ,("tomura@hyuki.com","Tomura")
                ,("mamoru@hyuki.com","Mamoru Takahashi")
                ]

class HtmlWriter:

    def __init__(self, fsock):
        self.__fsock = fsock

    def __print(self, string):
        try:
            print >>self.__fsock, string
        except IOError, (errno, strerror):
            print "I/O error(%s): %s" % (errno, strerror)

    def makeTitle(self, title):
        self.__print("<html><head><title>"+title.encode("utf_8")+"</title></head><body>")
        self.__print("<h1>"+title.encode("utf_8")+"</h1>")

    def makeParagraph(self, string):
        self.__print("<p>"+string.encode("utf_8")+"</p>")

    def makeLink(self, href, caption):
        self.makeParagraph('<a href="%s">%s</a>' % (href, caption))

    def makeMailTo(self, mailaddr, username):
        self.makeLink('mailto:'+mailaddr, username)

    def close(self):
        self.__print("</body></html>")
        self.__fsock.close()


class PageMaker:
    def __init__(self): raise Exception("Do not construct instances")

    @classmethod
    def makeLinkPage(cls, filename):
        try:
            fsock = open(filename, 'w')
        except IOError, (errno, strerror):
            print "I/O error(%s): %s" % (errno, strerror)

        htmlwriter = HtmlWriter(fsock)
        dbList = Database.getProperties()

        htmlwriter.makeTitle("Link page")
        for t in dbList:
            htmlwriter.makeMailTo(t[0], t[1])
        htmlwriter.close()


def main():
    PageMaker.makeLinkPage("linkpage.html")

if __name__=='__main__': main()

2010年11月25日木曜日

第14章 Chain of Responsibilityパターン : 責任のたらい回し



今回はChain of Responsibility。UIなどでよく使われるパターンだそうです。

というのも、UIはビューコンポーネントのようなものを入れ子にして構成されることが多く、
(WindowCanvasTextField 的な)
そのUIをユーザがクリック(タップ)したりキーボード操作したりする『要求』に対し、その要求をどのコンポーネントが処理するかは、普通『たらい回し』にされることが多いからです。
(上の例だと TextField → Canvas → Window)

すなわち、Chain of Responsibilityパターンは

  • (主にユーザなどによる)要求があり
  • その要求の処理先を動的に設定したい

時に最適なパターンと言えそうです。

また、Compositeパターンなどの再帰的構造でよく使うそうです。



実行結果
[0] is resolved by [Bob].
[33] is resolved by [Bob].
[66] is resolved by [Bob].
[99] is resolved by [Bob].
[132] is resolved by [Danie].
[165] is resolved by [Danie].
[198] is resolved by [Danie].
[231] is resolved by [Elmo].
[264] is resolved by [Fred].
[297] is resolved by [Elmo].
[330] cannot be resolved.
[363] is resolved by [Elmo].
[396] cannot be resolved.
[429] is resolved by [Charlie].
[462] cannot be resolved.
[495] is resolved by [Elmo].

ソースコード
#!/usr/bin/env python
# -*- coding: utf8 -*-

class Trouble:
    def __init__(self, number): self.number = number
    def __str__(self): return "[%d]" % (self.number)


class Support:
    def __init__(self): raise Exception("Abstract class")
    def __str__(self): return "[%s]" % self.name

    # final method - template method
    def support(self, trouble):
        if self._resolve(trouble):
            self._done(trouble)
        elif self.next is not None:
            self.next.support(trouble)
        else:
            self._fail(trouble)

    def _resolve(self, trouble): raise NotImplementedError
    def _done(self, trouble): print "%s is resolved by %s." % (trouble, self)
    def _fail(self, trouble): print "%s cannot be resolved." % (trouble)

    def setNext(self, next):
        self.next = next
        return self.next


class NoSupport(Support):
    def __init__(self, name):
        self.name = name
        self.next = None

    def _resolve(self, trouble): return False


class LimitSupport(Support):
    def __init__(self, name, limit):
        self.name = name
        self.limit = limit
        self.next = None

    def _resolve(self, trouble):
        return True if trouble.number < self.limit else False


class OddSupport(Support):
    def __init__(self, name):
        self.name = name
        self.next = None

    def _resolve(self, trouble):
        return True if trouble.number % 2 == 1 else False


class SpecialSupport(Support):
    def __init__(self, name, number):
        self.name = name
        self.number = number
        self.next = None

    def _resolve(self, trouble):
        return True if trouble.number == self.number else False


def main():
    m_alice = NoSupport("Alice")
    m_bob = LimitSupport("Bob", 100)
    m_charlie = SpecialSupport("Charlie", 429)
    m_danie = LimitSupport("Danie", 200)
    m_elmo = OddSupport("Elmo")
    m_fred = LimitSupport("Fred", 300)

    m_alice.setNext(m_bob).setNext(m_charlie).setNext(m_danie).setNext(m_elmo).setNext(m_fred)

    for i in xrange(0, 500, 33):
        m_alice.support(Trouble(i))

if __name__=='__main__': main()

2010年11月24日水曜日

第13章 Visitorパターン : 構造を渡り歩きながら仕事をする ~design patterns with python~



気がつけば、もう本の半分以上の量を消化していました。
今回はVisitorパターン。キーワードは『データ構造と処理を分離する』です。

例えば、データモデルクラスを作って、その中にデータ処理のメソッドなんかを追加すると、モデルクラスが肥大してしまうことがままありました。
(アクセサぐらいならまだいいのですが、シリアライザやパーサなんかもついつい入れがちでした)
そんな時は大抵別のクラスを用意してメソッド類を引越ししてやるのですが、色んなモデルクラスの色んな処理クラスが混在して見通しが悪くなりがちでした。
Visitorパターンは、こうしたケースに一貫した解決を与えるいい方法ではないかと思いました。

また書籍内の例を見ても分かりますが、CompositeパターンやDecoratorパターンなどの再帰的なパターンと相性が良さそうです。

--

visit()のオーバーロードは特に意味が無いようなので、メソッドを分けてあります。
iterator()は単にリストを返すようにしてあります。
練習問題13-1には、対象のファイルが存在するディレクトリのパスも併せて出力するように勝手に変更 してあります。

実行結果
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)

Making user entries...
/root (31850)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1850)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/Composite.java (200)
/root/usr/hanako (650)
/root/usr/hanako/memo.tex (300)
/root/usr/hanako/index.html (350)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)

HTML files are:
diary.html (100) in /root/usr/yuki
index.html (350) in /root/usr/hanako

ソースコード
#!/usr/bin/env python
# -*- coding: utf8 -*-

class FileTreatmentException(Exception):
    def __init__(self):
        pass

class __Element:
    def __init__(self): raise NotImplementedError
    def accept(self, visitor): raise NotImplementedError

class _Entry(__Element):
    def __init__(self): raise Exception("Abstract class")
    def getName(self): raise NotImplementedError
    def getSize(self): raise NotImplementedError
    def add(self, entry): raise FileTreatmentException
    def iterator(self): raise FileTreatmentException
    def __str__(self): return "%s (%d)" % (self.getName(), self.getSize())


class File(_Entry):
    def __init__(self, name, size):
        self.__name = name
        self.__size = size

    def getName(self): return self.__name
    def getSize(self): return self.__size

    def accept(self, visitor):
        visitor.visitFile(self)


class Directory(_Entry):
    def __init__(self, name):
        self.__name = name
        self.directory = []

    def getName(self): return self.__name
    def getSize(self): return sum([e.getSize() for e in self.directory])

    def add(self, entry):
        self.directory.append(entry)

    def iterator(self):
        return self.directory

    def accept(self, visitor):
        visitor.visitDirectory(self)


class _Visitor:
    def __init__(self): raise Exception("Abstract class")
    def visitFile(self, thefile): raise NotImplementedError
    def visitDirectory(self, directory): raise NotImplementedError

class ListVisitor(_Visitor):
    def __init__(self):
        self.__currentdir = ""

    def visitFile(self, thefile):
        print self.__currentdir+"/"+str(thefile)

    def visitDirectory(self, directory):
        print self.__currentdir+"/"+str(directory)

        # ディレクトリ内を走査するために一旦入る
        savedir = self.__currentdir
        self.__currentdir = self.__currentdir+"/"+directory.getName()

        for e in directory.iterator():
            e.accept(self)

        # 走査が終わったらディレクトリを出る
        self.__currentdir = savedir

#13-1
class FileFindVisitor(_Visitor):
    def __init__(self, pattern):
        self.__pattern = pattern
        self.__currentdir = ""
        self.__founds = [] #(file, dirname which contains the file)

    def visitFile(self, thefile):
        if thefile.getName().find(self.__pattern) is not -1:
            self.__founds.append((thefile, self.__currentdir))

    def visitDirectory(self, directory):
        savedir = self.__currentdir
        self.__currentdir = self.__currentdir+"/"+directory.getName()

        for e in directory.iterator():
            e.accept(self)

        self.__currentdir = savedir

    def getFoundFiles(self):
        return self.__founds


def main():
    try:
        print "Making root entries..."
        m_rootdir = Directory("root")
        m_bindir = Directory("bin")
        m_tmpdir = Directory("tmp")
        m_usrdir = Directory("usr")
        m_rootdir.add(m_bindir)
        m_rootdir.add(m_tmpdir)
        m_rootdir.add(m_usrdir)
        m_bindir.add(File("vi", 10000))
        m_bindir.add(File("latex", 20000))
        m_rootdir.accept(ListVisitor())

        print ""
        print "Making user entries..."
        m_yuki = Directory("yuki")
        m_hanako = Directory("hanako")
        m_tomura = Directory("tomura")
        m_usrdir.add(m_yuki)
        m_usrdir.add(m_hanako)
        m_usrdir.add(m_tomura)
        m_yuki.add(File("diary.html", 100))
        m_yuki.add(File("Composite.java", 200))
        m_hanako.add(File("memo.tex", 300))
        m_hanako.add(File("index.html", 350))
        m_tomura.add(File("game.doc", 400))
        m_tomura.add(File("junk.mail", 500))
        m_rootdir.accept(ListVisitor())

        #13-1
        print ""
        print "HTML files are:"
        ffv = FileFindVisitor(".html")
        m_rootdir.accept(ffv)
        for (f,d) in ffv.getFoundFiles():
            print "%s in %s" % (str(f), d)

    except FileTreatmentException:
        print "FileTreatmentException raised"

if __name__=='__main__': main()

2010年11月23日火曜日

第12章 Decoratorパターン : 飾り枠と中身の同一視 ~ design patterns with python ~



Decoratorパターンは前回のCompositeパターン
http://ksk77.blogspot.com/2010/11/11-composite.html
と同様、同一視がキーワードですね。

DecoratorパターンのUML(Fig 12-5 p.179)を見てみると、CompositeパターンのUML(Fig 11-3 p.164)とよく似ているのが分かります。
それもそのはずで、前回のファイルシステムの例で考えて見れば、ディレクトリを『飾り枠』としても同様の構造を記述できることが想像できます。またCompositeでは末端(Leaf)が分離され、Decoratorでは根っこ(ConcreteComponent)が分離されています。

Decoratorパターンは、言うなれば、中身が常に一つで、追加や取り出しが出来ないCompositeパターンでしょうか。…普通そんな風には考えないと思いますが。

Decoratorパターンの特徴は、どこを切ってもそこにはただ一つのComponentがあるという点でしょう。マトリョーシカのように。

--

SideBorderクラスのコンストラクタは引数の順番が逆な方が分かりやすいと思います。
あと今回は書籍内のコードを読まずに書いたので、実装が書籍に沿わないかもです。


実行結果
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+----------------+/
/|+--------------+|/
/||*+----------+*||/
/||*|konnichiwa|*||/
/||*+----------+*||/
/|+--------------+|/
/+----------------+/

ソースコード
#!/usr/bin/env python
# -*- coding: utf8 -*-

class Display:
    def __init__(self): raise Exception("Abstract class")
    def columns(self): raise NotImplementedError
    def rows(self): raise NotImplementedError
    def rowText(self, index): raise NotImplementedError
    def show(self):
        for i in xrange(self.rows()):
            print self.rowText(i)

class StringDisplay(Display):
    def __init__(self, string):
        self.__string = string

    def columns(self):
        return len(self.__string)

    def rows(self):
        return 1

    def rowText(self, index):
        return self.__string


class Border(Display):
    _display = None


class SideBorder(Border):
    def __init__(self, borderChar, display):
        self.__borderChar = borderChar
        self._display = display

    def columns(self):
        return self._display.columns() + 2

    def rows(self):
        return self._display.rows()

    def rowText(self, index):
        return self.__borderChar + self._display.rowText(index) + self.__borderChar


class FullBorder(Border):
    def __init__(self, display):
        self._display = display

    def columns(self):
        return self._display.columns() + 2

    def rows(self):
        return self._display.rows() + 2

    def rowText(self, index):
        if index == 0 or index == self.rows()-1:
            return "+" + "".join(["-" for i in xrange(self._display.columns())]) + "+"
        else:
            return "|" + self._display.rowText(index-1) + "|"



def main():
    # these data type are Display
    m_b1 = StringDisplay("Hello, world.")
    m_b2 = SideBorder("#", m_b1)
    m_b3 = FullBorder(m_b2)
    m_b4 = SideBorder("/", FullBorder(FullBorder(SideBorder("*", FullBorder(StringDisplay("konnichiwa"))))))
    m_b1.show()
    m_b2.show()
    m_b3.show()
    m_b4.show()

if __name__=='__main__': main()

第11章 Compositeパターン : 容器と中身の同一視



第11章はCompositeパターン。これは分かりやすい上に利用頻度も高そうなデザインパターンです。
再帰的なクラス構造が出てきたら、このパターンが適用できないかを常に意識したほうがいいでしょう。

  • Leaf役とComposite役とで共通な実装にしたければComponentに実装し、
  • それぞれで違う実装にしたければComponentで定義(宣言)だけして、LeafとCompositeそれぞれで実装すればいい
という点も、コードの見通しが良くなっていいですね。

add/remove/getChild は、例と同じようにComponent(親)にエラーとして実装し、Compositeでオーバーライドするのが好きです。


実行結果
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)

Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/Composite.java (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)

#11-2
/root (100)
/root/usr (100)
/root/usr/yuki (100)
/root/usr/yuki/Composite.java (100)
file = /root/usr/yuki/Composite.java
yuki = /root/usr/yuki

ソースコード
#!/usr/bin/env python
# -*- coding: utf8 -*-

class FileTreatmentException(Exception):
    def __init__(self):
        print "FileTreatmentException raised"


class Entry:
    def __init__(self): raise Exception("Abstract class")
    def getName(self): raise NotImplementedError
    def getSize(self): raise NotImplementedError
    def printList(self): self._printList("")
    def _printList(self, prefix): raise NotImplementedError
    def add(self, entry): raise FileTreatmentException
    def __str__(self): return "%s (%d)" % (self.getName(), self.getSize())

    #11-2
    def getFullName(self):
        entry = self
        fullname = ""
        while entry is not None:
            fullname = "/" + entry.getName() + fullname
            entry = entry.parent
        return fullname


class File(Entry):
    def __init__(self, name, size):
        self.__name = name
        self.__size = size
        self.parent = None

    def getName(self): return self.__name
    def getSize(self): return self.__size
    def _printList(self, prefix): print prefix+"/"+str(self)


class Directory(Entry):
    def __init__(self, name):
        self.__name = name
        self.directory = []
        self.parent = None

    def getName(self): return self.__name
    def getSize(self): return sum([e.getSize() for e in self.directory])

    def _printList(self, prefix):
        print prefix+"/"+str(self)
        for e in self.directory: e._printList(prefix+"/"+self.__name)

    def add(self, entry):
        self.directory.append(entry)
        entry.parent = self #11-2


def main():
    print "Making root entries..."
    m_rootdir = Directory("root")
    m_bindir = Directory("bin")
    m_tmpdir = Directory("tmp")
    m_usrdir = Directory("usr")
    m_rootdir.add(m_bindir)
    m_rootdir.add(m_tmpdir)
    m_rootdir.add(m_usrdir)
    m_bindir.add(File("vi", 10000))
    m_bindir.add(File("latex", 20000))
    m_rootdir.printList()

    print ""
    print "Making user entries..."
    m_yuki = Directory("yuki")
    m_hanako = Directory("hanako")
    m_tomura = Directory("tomura")
    m_usrdir.add(m_yuki)
    m_usrdir.add(m_hanako)
    m_usrdir.add(m_tomura)
    m_yuki.add(File("diary.html", 100))
    m_yuki.add(File("Composite.java", 200))
    m_hanako.add(File("memo.tex", 300))
    m_tomura.add(File("game.doc", 400))
    m_tomura.add(File("junk.mail", 500))
    m_rootdir.printList()

    #11-2
    print ""
    print "#11-2"
    m_rootdir = Directory("root");
    m_usrdir = Directory("usr");
    m_rootdir.add(m_usrdir);
    m_yuki = Directory("yuki");
    m_usrdir.add(m_yuki);
    m_file = File("Composite.java", 100);
    m_yuki.add(m_file);
    m_rootdir.printList();
    print "file = " + m_file.getFullName()
    print "yuki = " + m_yuki.getFullName()

if __name__=='__main__': main()

2010年11月22日月曜日

python で 選択ソート・クイックソート



前回のStrategyパターン
http://ksk77.blogspot.com/2010/11/10-strategy.html
で、練習問題に挙げられていた『ソートアルゴリズムを切り替えられるソータクラス』をpythonで書いてみた。
選択ソート、クイックソートだけです。間違ってたらごめんなさい。


実行結果
Dumpty, Bowman, Carroll, Elfland, Alice
Alice, Bowman, Carroll, Dumpty, Elfland
Dumpty, Bowman, Carroll, Elfland, Alice
Alice, Bowman, Carroll, Dumpty, Elfland
4, 6, 2, 4, 7, 0, 1, 12
0, 1, 2, 4, 4, 6, 7, 12
4, 6, 2, 4, 7, 0, 1, 12
0, 1, 2, 4, 4, 6, 7, 12

ソースコード

sorter.py
# -*- coding: utf8 -*-

class __Sorter:
    def __init__(self): raise NotImplementedError
    def sort(self, data): raise NotImplementedError

class SelectionSorter(__Sorter):
    def __init__(self): pass
    def sort(self, data):
        if len(data) <= 1: return data
        minx = data.pop(data.index(min(data)))
        return [minx] + self.sort(data)

class QuickSorter(__Sorter):
    def __init__(self): pass
    def sort(self, data):
        return self.sort(filter(lambda x: x < data[0], data)) + filter(lambda x: x == data[0], data) + self.sort(filter(lambda x: x > data[0], data)) if len(data) > 1 else data


from copy import copy
class SortAndPrint:
    def __init__(self, data, sorter):
        self.data = data
        self.sorter = sorter

    def execute(self):
        self.__draw(self.data)
        sorted_data = self.sorter.sort(copy(self.data))
        self.__draw(sorted_data)

    def __draw(self, d):
        print ", ".join(map(str, d))

main.py
#!/usr/bin/env python
# -*- coding: utf8 -*-

from sorter import SortAndPrint, SelectionSorter, QuickSorter

def main():
    data = ["Dumpty", "Bowman", "Carroll", "Elfland", "Alice"]
    sap = SortAndPrint(data, SelectionSorter())
    sap.execute()

    sap = SortAndPrint(data, QuickSorter())
    sap.execute()

    data = [4,6,2,4,7,0,1,12]
    sap = SortAndPrint(data, SelectionSorter())
    sap.execute()

    sap = SortAndPrint(data, QuickSorter())
    sap.execute()

if __name__=='__main__': main()

2010年11月21日日曜日

第10章 Strategyパターン : アルゴリズムをごっそり切り替える



Strategyパターンは、『アルゴリズム』を入れ替えるパターン。UMLの雰囲気なんかは目新しくないが、実用性は高そう。パーサとかスクレイピングとかね。
例ではStrategyがもろ『戦略』なんだけど、これが却って混乱させるというか。練習問題のソートのやつのほうが応用という意味では適していそう。

コード書いてて、自分でじゃんけんアルゴリズムを考えたくなるのは性なんでしょうかね。でも思いつかなかったので書いてないです。

何となく、囚人のジレンマに適用できるくらいまで抽象化してみようかと考えています。


ソースコード

strategy.py
# -*- coding: utf8 -*-

class __Strategy: #interface
    def __init__(self): raise NotImplementedError
    def nextHand(self): raise NotImplementedError
    def study(self, win): raise NotImplementedError

import hand
import random as random1
class WinningStrategy(__Strategy):
    def __init__(self, seed):
        random1.seed(seed)
        self.won = False
        self.previousHand = None

    def nextHand(self):
        if not self.won:
            self.previousHand = hand.getHand(random1.randint(0,2))
        return self.previousHand

    def study(self, win):
        self.won = win


import random as random2
class ProbStrategy(__Strategy):
    def __init__(self, seed):
        random2.seed(seed)
        self.history = [[1,1,1] for i in xrange(3)]
        self.prevHandValue = 0
        self.currentHandValue = 0

    def nextHand(self):
        def listConcat(listOfLists): return reduce(lambda a,b: a+b, listOfLists,[])
        self.prevHandValue = self.currentHandValue
        #ex. [3,2,5] -> [[0,0,0],[1,1],[2,2,2,2,2]] -> [0,0,0,1,1,2,2,2,2,2] -> choice one
        self.currentHandValue = random2.choice(listConcat([[i for dummy in xrange(x)] for i,x in enumerate(self.history[self.currentHandValue])]))
        return hand.getHand(self.currentHandValue)

    def study(self, win):
        if win:
            self.history[self.prevHandValue][self.currentHandValue] += 1
        else:
            self.history[self.prevHandValue][(self.currentHandValue+1)%3] += 1
            self.history[self.prevHandValue][(self.currentHandValue+2)%3] += 1

player.py
# -*- coding: utf8 -*-

class Player:
    def __init__(self, name, strategy):
        self.name = name
        self.strategy = strategy
        self.gamecount = self.wincount = self.losecount = 0

        self.nextHand = strategy.nextHand

    def win(self):
        self.strategy.study(True)
        self.wincount+=1
        self.gamecount+=1

    def lose(self):
        self.strategy.study(False)
        self.losecount+=1
        self.gamecount+=1

    def even(self):
        self.gamecount+=1

    def __str__(self):
        return '[%s: %d games, %d win, %d lose]' % (self.name, self.gamecount, self.wincount, self.losecount,)

main.py
#!/usr/bin/env python
# -*- coding: utf8 -*-

from player import Player
from strategy import WinningStrategy, ProbStrategy
import sys

def main():
    if not len(sys.argv) == 3:
        print "Usage: python main.py randomseed1 randomseed2"
        print "Example: python main.py 314 15"
        sys.exit(0)

    seeds = map(long, sys.argv[1:3])

    player1 = Player("Taro", WinningStrategy(seeds[0]))
    player2 = Player("Hana", ProbStrategy(seeds[1]))

    for i in xrange(10000):
        nextHand1 = player1.nextHand()
        nextHand2 = player2.nextHand()
        if nextHand1.isStrongerThan(nextHand2):
            print "Winner:", player1
            player1.win()
            player2.lose()
        elif nextHand2.isStrongerThan(nextHand1):
            print "Winner:", player2
            player1.lose()
            player2.win()
        else:
            print "Even..."
            player1.even()
            player2.even()

    print "Total result:"
    print player1
    print player2


if __name__=='__main__': main()

2010年11月20日土曜日

第9章 Bridgeパターン : 機能の階層と実装の階層を分ける [デザインパターン with python]



またまた面白いデザインパターンが登場。Bridgeパターンでは

  • 機能のクラス階層
  • 実装のクラス階層

という単語がしばしば登場する。本を読み、演習を解けば、この違いははっきりと分かるようになるし、これらを分ける理由も理解できる。

大事なことは、そもそもプログラミングするときに、

  • 機能を書く ― 実装を書く

という発想がなく、単に

  • これはクラスだからextends
  • これはインターフェイスだからimplements

くらいしか考えてなかったことだろう。
(機能―実装 と クラス―インターフェイス とを対比させるのは明らかにおかしいけど)
機能/実装という見方でコードを読んだり書いたりすると、プログラマとして一歩前進できそうだ。

実装のクラス階層をOS依存で考える例や、継承と委譲の考え方の対比なんかも勉強になりました。


実行結果
+-------------+
|Hello, Japan.|
+-------------+
+----------------+
|Hello, Universe.|
+----------------+
+----------------+
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
+----------------+
+----------------------------------+
|How many times have I been called?|
|How many times have I been called?|
|How many times have I been called?|
|How many times have I been called?|
+----------------------------------+
この味は

うそをついてる味なんだなあ 



みつを 

< >
< * >
< * * >
< * * * >
| -
| ## -
| ## ## -
| ## ## ## -
| ## ## ## ## -
| ## ## ## ## ## -

ソースコード
#!/usr/bin/env python
# -*- coding: utf8 -*-

# 機能のクラス階層

class Display:
    def __init__(self, impl): self.impl = impl
    def prepare(self): self.impl.rawPrepare()
    def draw(self): self.impl.rawDraw()
    def finish(self): self.impl.rawFinish()
    def display(self):
        self.prepare()
        self.draw()
        self.finish()

class CountDisplay(Display):
    def multiDisplay(self, times):
        self.prepare()
        for i in xrange(times): self.draw()
        self.finish()

#9-1
from random import randint
class RandomDisplay(CountDisplay):
    def randomDisplay(self, max_times): self.multiDisplay(randint(0,max_times-1))

#9-3
class IncreaseDisplay(CountDisplay):
    def increaseDisplay(self, times):
        for i in xrange(times): self.multiDisplay(i)


# 実装のクラス階層

class DisplayImpl:
    def __init__(self): raise Exception("Abstract class")
    def rawPrepare(self): raise NotImplementedError
    def rawDraw(self): raise NotImplementedError
    def rawFinish(self): raise NotImplementedError

class StringDisplayImpl(DisplayImpl):
    def __init__(self, string): self.string = string
    def rawPrepare(self): self.__printLine()
    def rawDraw(self): print "|%s|" % self.string
    def rawFinish(self): self.__printLine()
    def __printLine(self): print '+%s+' % ''.join(['-' for i in xrange(len(self.string))])

#9-2
class FileDisplayImpl(DisplayImpl):
    import sys
    def __init__(self, filename):
        self.filename = filename
        self.fileobj = None
    def rawPrepare(self):
        try:
            self.fileobj = open(self.filename, 'r')
        except IOError, (errno, strerror):
            print "I/O error(%s): %s" % (errno, strerror)
            sys.exit(0)
    def rawDraw(self):
        self.fileobj.seek(0) # reset
        for line in self.fileobj: print line
    def rawFinish(self): self.fileobj.close()

#9-3
class CharDisplayImpl(DisplayImpl):
    def __init__(self, head, body, foot):
        self.head = head
        self.body = body
        self.foot = foot
    def rawPrepare(self): print self.head,
    def rawDraw(self): print self.body,
    def rawFinish(self): print self.foot



def main():
    d1 = Display(StringDisplayImpl('Hello, Japan.'))
    d2 = CountDisplay(StringDisplayImpl('Hello, Universe.'))
    d1.display()
    d2.display()
    d2.multiDisplay(5)

    #9-1
    d3 = RandomDisplay(StringDisplayImpl('How many times have I been called?'))
    d3.randomDisplay(5)

    #9-2
    d4 = CountDisplay(FileDisplayImpl('mitsuo.txt'))
    d4.multiDisplay(1)

    #9-3
    d5 = IncreaseDisplay(CharDisplayImpl('<','*','>'))
    d5.increaseDisplay(4)
    d6 = IncreaseDisplay(CharDisplayImpl('|','##','-'))
    d6.increaseDisplay(6)

if __name__=='__main__': main()

第8章 Abstract Factoryパターン : 関連する部品を組み合わせて製品を作る w/ python



今回はAbstract Factoryパターン。このデザインパターンはこれまでのパターンの応用という位置づけになっている(と思う)。とはいえ、やっていることは第4章のFactory Method(http://ksk77.blogspot.com/2010/11/4-factory-method.html)の拡張で、

  • Productを複数個に増やし、
  • それらを(抽象)部品として扱い、
  • factoryMethod()をその(抽象)部品から組み立てた(抽象)製品を作るメソッドに置き換える

と考えると分かりやすい。この『部品から製品を』というところで、前章のBuilderパターンhttp://ksk77.blogspot.com/2010/11/7-builder-with-python.htmlの経験が生きてくるのかこないのか、といった感じ。

今回のデザインパターンで強く意識させられたのは、デザインパターンの大きなメリットの一つである

  • 依存しない/既存のソースコードを一切変更しない

というところ。練習問題で、これが効率・生産性・心理的にどれだけ意味があるかを実感しました。
あと、内包表記様様って感じです。


ソースコード

factory.py
# -*- coding: utf8 -*-

class Factory:
    @classmethod
    def getFactory(cls, classname):
        def get_class( kls ):
            parts = kls.split('.')
            module = ".".join(parts[:-1])
            m = __import__( module )
            for comp in parts[1:]:
                m = getattr(m, comp)
            return m
        try:
            C = get_class(classname)
            return C()
        except:
            print "invalid classname -> %s" % (classname)
            raise Exception()

    def createLink(self, caption, url): raise NotImplementedError
    def createTray(self, caption): raise NotImplementedError
    def createPage(self, title, author): raise NotImplementedError

class Item:
    def makeHTML(self): raise NotImplementedError

class Link(Item):
    def __init__(self, caption, url):
        self.caption = caption
        self.url = url

class Tray(Item):
    def __init__(self, caption):
        self.caption = caption
        self.tray = []
    def add(self, item): self.tray.append(item)

class Page:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.contents = []

    def add(self, item): self.contents.append(item)
    def output(self): print self.makeHTML()
    def makeHTML(self): raise NotImplementedError

listfactory.py
# -*- coding: utf8 -*-

from factory import Factory, Link, Tray, Page

class ListFactory(Factory):
    def createLink(self, caption, url): return ListLink(caption, url)
    def createTray(self, caption): return ListTray(caption)
    def createPage(self, title, author): return ListPage(title, author)

class ListLink(Link):
    def makeHTML(self): return '<li><a href="%s">%s</a></li>' % (self.url, self.caption)

class ListTray(Tray):
    def makeHTML(self): return '<li>%s<ul>%s</ul></li>' % (self.caption, ''.join([i.makeHTML() for i in self.tray]))

class ListPage(Page):
    def makeHTML(self): return '<html><head><title>%s</title></head><body><ul>%s</ul><hr><address>%s</address></hr></body></html>' % (self.title, ''.join([i.makeHTML() for i in self.contents]), self.author,)

tablefactory.py
# -*- coding: utf8 -*-

from factory import Factory, Link, Tray, Page

class TableFactory(Factory):
    def createLink(self, caption, url): return TableLink(caption, url)
    def createTray(self, caption): return TableTray(caption)
    def createPage(self, title, author): return TablePage(title, author)

class TableLink(Link):
    def makeHTML(self): return '<td><a href="%s">%s</a></td>' % (self.url, self.caption)

class TableTray(Tray):
    def makeHTML(self): return '<td><table border="1" width="100%%"><tr><th colspan="%d">%s</th></tr><tr>%s</tr></table></td>' % (len(self.tray), self.caption, ''.join([i.makeHTML() for i in self.tray]))

class TablePage(Page):
    def makeHTML(self): return '<html><head><title>%s</title></head><body><table border="3"><tbody>%s</tbody></table><hr><address>%s</address></hr></body></html>' % (self.title, ''.join(['<tr>'+i.makeHTML()+'</tr>' for i in self.contents]), self.author,)

main.py
#!/usr/bin/env python
# -*- coding: utf8 -*-

from factory import Factory
import sys

def main():
    try:
        factory = Factory.getFactory(sys.argv[1])
    except:
        print "Usage: python main.py class.name.of.ConcreteFactory"
        print "Example 1: python main.py listfactory.ListFactory"
        print "Example 2: python main.py tablefactory.TableFactory"
        sys.exit(0)

    asahi = factory.createLink("朝日新聞", "http://www.asahi.com/") # Link's instance
    yomiuri = factory.createLink("読売新聞", "http://www.yomiuri.co.jp/") # Link's instance

    us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/") # Link's instance
    jp_yahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.jp/") # Link's instance
    excite = factory.createLink("Excite", "http://www.excite.com/") # Link's instance
    google = factory.createLink("Google", "http://www.google.com/") # Link's instance

    traynews = factory.createTray("新聞") # Tray's instance
    traynews.add(asahi)
    traynews.add(yomiuri)

    trayyahoo = factory.createTray("Yahoo!") # Tray's instance
    trayyahoo.add(us_yahoo)
    trayyahoo.add(jp_yahoo)

    traysearch = factory.createTray("サーチエンジン") # Tray's instance
    traysearch.add(trayyahoo)
    traysearch.add(excite)
    traysearch.add(google)

    page = factory.createPage("LinkPage", "結城 浩") # Page's instance
    page.add(traynews)
    page.add(traysearch)
    page.output()

if __name__=="__main__": main()

2010年11月18日木曜日

第7章 Builderパターン : 複雑なインスタンスを組み立てる with python



BuilderパターンはTemplate Methodパターン(http://ksk77.blogspot.com/2010/11/3-template-method.html)に似ていて、Template Methodの応用と考えたほうが分かりやすいです。
異なる点は:

  • Template Method … テンプレートメソッドをスーパークラスが実装している
  • Builder … テンプレートメソッドを他のクラス(Director)が実装している


ここでいうテンプレートメソッドは、第3章におけるdisplay()に相当します。
Client(main)はBuilder役を知らず、Director役はBuilderしか知らない、という関係が結合度を小さくすることに寄与するのであります。

ソースコード
日本語エンコード処理まわりが非常に雑なのでその辺は参考にしないでください。

#!/usr/bin/env python
# -*- coding: utf8 -*-

class Director:
    def __init__(self, builder): self.builder = builder
    def construct(self):
        self.builder.makeTitle("Greeting")
        self.builder.makeString(u"朝から昼にかけて")
        self.builder.makeItems([u"おはようございます", u"こんにちは"])
        self.builder.makeString(u"夜に")
        self.builder.makeItems([u"こんばんは", u"おやすみなさい", u"さようなら"])
        self.builder.close()

class __Builder:
    def makeTitle(self, title): raise NotImplementedError
    def makeString(self, string): raise NotImplementedError
    def makeItems(self, items): raise NotImplementedError
    def close(self): raise NotImplementedError

import StringIO
class TextBuilder(__Builder):
    def __init__(self): self.buf = StringIO.StringIO()
    def makeTitle(self, title):
        print >>self.buf, "========================"
        print >>self.buf, "["+title+"]"
        print >>self.buf, ""
    def makeString(self, string):
        print >>self.buf, u"■"+string
        print >>self.buf, ""
    def makeItems(self, items):
        for i in items: print >>self.buf, u" ・"+i
        print >>self.buf, ""
    def close(self):
        print >>self.buf, "========================"
    def getResult(self):
        return self.buf.getvalue()

class HTMLBuilder(__Builder):
    def makeTitle(self, title):
        try:
            self.fsock = open(title+".html", 'w')
        except IOError, (errno, strerror):
            print "I/O error(%s): %s" % (errno, strerror)
        print >>self.fsock, "<html><head><title>"+title.encode("utf_8")+"</title></head><body>"
        print >>self.fsock, "<h1>"+title.encode("utf_8")+"</h1>"
    def makeString(self, string):
        print >>self.fsock, "<p>"+string.encode("utf_8")+"</p>"
    def makeItems(self, items):
        print >>self.fsock, "<ul>"
        for i in items: print >>self.fsock, "<li>"+i.encode("utf_8")+"</li>"
        print >>self.fsock, "</ul>"
    def close(self):
        print >>self.fsock, "</body></html>"
        self.fsock.close()
    def getResult(self):
        return self.fsock.name

def main():
    import sys, getopt
    opts,args = getopt.getopt(sys.argv[1:],'M:')
    for key,val in opts:
        if key == '-M':
            if val == "plain":
                textbuilder = TextBuilder()
                director = Director(textbuilder)
                director.construct()
                print textbuilder.getResult()
                return
            elif val == "html":
                htmlbuilder = HTMLBuilder()
                director = Director(htmlbuilder)
                director.construct()
                print htmlbuilder.getResult()+u"が作成されました"
                return
    print "Usage: python 7_builder.py -M plain"
    print "Usage: python 7_builder.py -M html"
    return

if __name__=='__main__': main()

2010年11月16日火曜日

第6章 Prototypeパターン : コピーしてインスタンスを作る with python



早いものでもう第6章。今回はPrototypeパターンです。

第4章のFactory Methodパターン( http://ksk77.blogspot.com/2010/11/4-factory-method.html )に似ているのですが、大きく違うところが一点。

  • 『生成』ではなく『コピー』

この違いのため、以下のように焦点が異なっています。

  • 前回は作る側(Factory)に焦点を当てて生成のメソッド(factoryMethod)なんかを作っていた
  • 今回は作られる側(Prototype)に焦点を当ててコピーを作るメソッド(createClone)なんかを作っている


こうやって自分なりの解釈を付けていきながら読み進めていこう。

以下、pythonでの実装。(と書くのも一々面倒なのでタイトルにpythonって書いておきました。) pythonではオブジェクトのコピーを copy モジュールに独立に用意しているので分かりやすいです。
なお、練習問題6-1を参考に、PrototypeにcreateClone()を実装してあります。

実行結果

"Hello, world."
 ~~~~~~~~~~~~~ 
*****************
* Hello, world. *
*****************
/////////////////
/ Hello, world. /
/////////////////

ソースコード

framework.py
# -*- coding: utf8 -*-

import copy

class Manager:
    def __init__(self): self.__showcase = dict()
    def register(self, name, proto): self.__showcase[name] = proto
    def create(self, name): return copy.deepcopy(self.__showcase[name])

class Prototype:
    def use(self): raise NotImplementedError
    def createClone(self): return copy.deepcopy(self)

main.py
#!/usr/bin/env python
# -*- coding: utf8 -*-

from framework import Manager, Prototype

class MessageBox(Prototype):
    def __init__(self, decochar): self.decochar = decochar
    def use(self, message):
        import sys
        for i in xrange(len(message)+3): sys.stdout.write(self.decochar)
        print self.decochar
        print "%c %s %c" % (self.decochar, message, self.decochar)
        for i in xrange(len(message)+3): sys.stdout.write(self.decochar)
        print self.decochar

class UnderlinePen(Prototype):
    def __init__(self, ulchar): self.ulchar = ulchar
    def use(self, message):
        import sys
        print "\""+message+"\""
        sys.stdout.write(" ")
        for i in xrange(len(message)): sys.stdout.write(self.ulchar)
        print " "

def main():
    # prepare
    manager = Manager()
    upen = UnderlinePen("~")
    mbox = MessageBox("*")
    sbox = MessageBox("/")
    manager.register("strong message", upen)
    manager.register("warning box", mbox)
    manager.register("slash box", sbox)

    # create
    p1 = manager.create("strong message")
    p1.use("Hello, world.")
    p2 = manager.create("warning box")
    p2.use("Hello, world.")
    p3 = manager.create("slash box")
    p3.use("Hello, world.")

if __name__=='__main__': main()

2010年11月15日月曜日

第5章 Singletonパターン : たった1つのインスタンス



第5章はSingletonパターン。このデザインパターンは分かりやすいですね、実際に使う場面もすぐ浮かんできました(DBへのコネクション用オブジェクトとか)。

いつも通り、pythonによる実装を試みたのですが、上手いこといかず苦労しました。"python singleton"でググると色んな解決を見ることが出来ますが、pythonはそもそもprivateなクラスやコンストラクタを作れないので(作る必要がないので)実装が難しいようです。

そんな折、stackoverflowにこんなページが。


ここに『I would still just put methods in a module, and consider the module as the singleton.』と書いてあります。つまりこういうことです。

ソースコード

singleton.py
# -*- coding: utf8 -*-

instance = object()
print u"生成しました"

def getInstance(): return instance

main.py
#!/usr/bin/env python
# -*- coding: utf8 -*-

import singleton

def main():
    print "start."
    obj1 = singleton.getInstance()
    obj2 = singleton.getInstance()
    if obj1 == obj2:
        print u"同じ"
    else:
        print u"違う"
    print "end."

if __name__=='__main__': main()

実行結果
生成しました
start.
同じ
end.

本当にこれでいいのだろうか。分からない 笑

2010年11月14日日曜日

第4章 Factory Methodパターン : インスタンス作成をサブクラスにまかせる



本章は前回(http://ksk77.blogspot.com/2010/11/3-template-method.html)のTemplate Methodパターンの応用と考えると分かりやすい。応用方法は

  • 『インスタンス作成』をnewではなくTemplate Methodとして実装する

ことで、何が嬉しいのかというと

  • 『インスタンス作成』という機能の自由度が増え、サービスに合わせた柔軟な設計が可能になる

ことだ、というふうに理解しました。本書のIDカードの例もそうですが、

  • サービス的な見方(IDカードを発行する)
  • プログラム的な見方(new IDCard())

は乖離していることが多く、サービスを開発するたびにその都度実装していては大変なので、Factory Methodパターンは有用だなと思いますた。
逆に考えれば、全てのクラスがコンストラクタというメソッドを持っているからこそ、このデザインパターンが独立しているんでしょうね

--

いつも通り、pythonによる実装↓

実行結果

結城浩が使っています
とむらが使っています
佐藤花子が使っています

ソースコード

factory.py
# -*- coding: utf8 -*-

class Factory:
    def __init__(self): raise Exception("abstract class")
    def createProduct(self, owner): raise NotImplementedError
    def registerProduct(self, product): raise NotImplementedError
    def create(self, owner):
        product = self.createProduct(owner)
        self.registerProduct(product)
        return product

class Product:
    def __init__(self): raise Exception("abstract class")
    def use(self): raise NotImplementedError

idcard.py, 含main
#!/usr/bin/env python
# -*- coding: utf8 -*-

from framework import Factory, Product

class IDCardFactory(Factory):
    def __init__(self): self.owners = []
    def createProduct(self, owner):
        idcard = IDCard(owner)
        return idcard
    def registerProduct(self, product): self.owners.append(product)

class IDCard(Product):
    def __init__(self, owner): self.owner = owner
    def use(self): print self.owner+u"が使っています"


def main():
    factory = IDCardFactory() # Factory's instance
    card1 = factory.create(u"結城浩") # Product's instance
    card2 = factory.create(u"とむら") # Product's instance
    card3 = factory.create(u"佐藤花子") # Product's instance
    card1.use()
    card2.use()
    card3.use()

if __name__=='__main__': main()