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()

2010年11月13日土曜日

第3章 Template Methodパターン : 具体的な処理をサブクラスにまかせる



この章の内容は、本文中にある次の一言に尽きます。
処理の骨組みをスーパークラスで記述し、具体的な肉付けをサブクラスで行っています。
新鮮に感じた部分は次の箇所。
  • 処理そのもの(templateMethod)はスーパークラスに書き、その処理のアルゴリズムを抽象メソッドで構築する
  • 抽象的なスーパークラスからの視点でサブクラスを見る
デザインパターンを勉強していて一番面白いのがこの新鮮さ。コードを書く意欲を駆り立てられます。

--

いつも通り、pythonで書いてみました。

実行結果
く け け け け け ガチャ
+--------------+
|逃げちゃダメだ|
|逃げちゃダメだ|
|逃げちゃダメだ|
|逃げちゃダメだ|
|逃げちゃダメだ|
+--------------+
+--------------------------------+
|It is not good ..running away...|
|It is not good ..running away...|
|It is not good ..running away...|
|It is not good ..running away...|
|It is not good ..running away...|
+--------------------------------+


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

class AbstractDisplay:
    def __init__(self): raise NotImplementedError
    def openIt(self): raise NotImplementedError
    def printIt(self): raise NotImplementedError
    def closeIt(self): raise NotImplementedError
    def display(self):
        self.openIt()
        for i in xrange(5): self.printIt()
        self.closeIt()

class CharDisplay(AbstractDisplay):
    def __init__(self, ch): self.ch = ch
    def openIt(self): print u"く",
    def printIt(self): print self.ch,
    def closeIt(self): print u"ガチャ"

class StringDisplay(AbstractDisplay):
    def __init__(self, string): self.string = string
    def openIt(self): self.__printLine()
    def printIt(self): print "|%s|" % self.string
    def closeIt(self): self.__printLine()
    def __printLine(self):
        import sys
        sys.stdout.write('+')
        for i in xrange(len(self.string.encode("sjis"))): # byte-length of sjis is 2 while that of utf8 is 3
            sys.stdout.write('-')
        print '+'


def main():
    d1 = CharDisplay(u"け") # AbstractDisplay's instance
    d2 = StringDisplay(u"逃げちゃダメだ") # AbstractDisplay's instance
    d3 = StringDisplay("It is not good ..running away...") # AbstractDisplay's instance
    d1.display()
    d2.display()
    d3.display()

if __name__=='__main__': main()

pythonで書くのはpythonの勉強にはなるけど、型がないのでクラス構造が分かりにくくなってしまう問題があります。かといってC++で書くのもなあ

2010年11月12日金曜日

第2章 Adapterパターン : 一皮かぶせて再利用



早速一日サボってしまった…めげずに頑張ろう。

今回はAdapterパターン。自分の理解を書くとこんなところです。

  • 実際に動作する既存クラスに新しいインターフェイスを適用する場合に使う
  • Adapterは新しいインターフェイスにどの既存インターフェイスを当てるかを書くだけで、ここに具体的なロジックは実装しない
著書では二つの手法が紹介されています。私感としてはこんなところです。
  • 継承…AdapterがAdapteeとほぼ同じ感じであれば使う
  • 委譲…AdapterとAdapteeが微妙に違っていたり、複数のAdapteeがいたりしたら使う
曖昧な日本語だね☆

で、前回同様pythonによるコーディングをしてみた。内容は本と全く同じ。出来れば自分で別の例を試したいのだけど、例が思い浮かばないんです。

実行結果
(Hello)
*Hello*
(World)
*World*
ソースコード
#!/usr/bin/env python

class Banner:
    def __init__(self, string=""): self.string = string
    def showWithParen(self): print "(%s)" % self.string
    def showWithAster(self): print "*%s*" % self.string

# Interface
class __Print:
    def __init__(self): raise Exception("this is an interface!")
    def printWeak(self): raise NotImplementedError
    def printStrong(self): raise NotImplementedError

# Class
class Print:
    def printWeak(self): raise NotImplementedError
    def printStrong(self): raise NotImplementedError

# Case by interface - inheritance
class PrintBanner1(Banner, __Print):
    #def __init__(self, string): super(PrintBanner1, self).__init__(string)
    def printWeak(self): self.showWithParen()
    def printStrong(self): self.showWithAster()

# Case by class - delegation
class PrintBanner2(Print):
    def __init__(self, string): self.banner = Banner(string)
    def printWeak(self): self.banner.showWithParen()
    def printStrong(self): self.banner.showWithAster()


def main():
    pb1 = PrintBanner1("Hello")
    pb1.printWeak()
    pb1.printStrong()

    pb2 = PrintBanner2("World")
    pb2.printWeak()
    pb2.printStrong()

if __name__=='__main__': main()
PS.
一箇所コメントアウトしてあるところは、コンストラクタ内で明示的にスーパークラスのコンストラクタを呼んでいるのですが、このコメントアウトを外すと次のようなコンパイルエラーが出ます。原因は…分かりませんでした。
TypeError: super() argument 1 must be type, not classobj

2010年11月10日水曜日

第1章 Iteratorパターン : 一つ一つ数え上げる



これは分かりやすかった。引っ掛かったところは
  • IteratorはAggregate(集合体)があって初めて意味を持つので、その集合体にIteratorを作成するメソッドを持たせる
  • next()は現在のインデックスの値を返す
  • hasNext()はnext()が呼べるかどうかという真偽値を返す
といったところ。


応用1 ― pythonで実装

練習問題をやってもいいのだけど、ここのところpythonを使う機会が多いのでpythonでIteratorパターンを書いてみようと思います。
教本と同じBookshelfを実装してみるとこんな感じになりました。
#!/usr/bin/env python

class __Aggregator:
    def __init__(self): raise Exception('abstract class')
    def iterator(self): raise Exception('iterator() must be implemented')

class __Iterator:
    def __init__(self): raise Exception('abstract class')
    def next(self): raise Exception('next() must be implemented')
    def hasNext(self): raise Exception('hasNext() must be implemented')

class Bookshelf(__Aggregator):
    def __init__(self): self.__books = []
    def getBookAt(self, index): return self.__books[index]
    def appendBook(self, book): self.__books.append(book)
    def getLength(self): return len(self.__books)
    def iterator(self): return BookshelfIterator(self)

class Book:
    def __init__(self, name): self.__name = name
    def getName(self): return self.__name

class BookshelfIterator(__Iterator):
    def __init__(self, bookshelf):
        self.__bookshelf = bookshelf
        self.__index = 0

    def next(self):
        book = self.__bookshelf.getBookAt(self.__index)
        self.__index += 1
        return book

    def hasNext(self): return self.__index < self.__bookshelf.getLength()

def main():
    bookshelf = Bookshelf()
    bookshelf.appendBook(Book("Around the World in 80 Days"))
    bookshelf.appendBook(Book("Bible"))
    bookshelf.appendBook(Book("Cinderella"))
    bookshelf.appendBook(Book("Daddy-Long-Legs"))
    it = bookshelf.iterator()
    while it.hasNext():
        print it.next().getName()

if __name__=='__main__': main()

pythonにはJavaでいうインターフェイスや抽象クラスの概念はないので、アンダースコアを2つつけて明示化してあります。それ以外は何の変哲もなし。実行結果も同じ。


応用2 ― もっと簡単なpythonでの実装

調べてみると、pythonにはイテレータを実装する仕組みが既にあった。
http://www.python.jp/doc/2.5/tut/node11.html#SECTION0011800000000000000000
(というかあったけどあえて自分で書いてた。勉強ですからね)
前のとの違いは
  • hasNext()は無くて、next()を呼んだときに値がなければ例外StopIterationを投げる
  • 利用側がnext()を直接呼ばない。代わりにfor loopを使う
#!/usr/bin/env python

class __Aggregator:
    def __init__(self): raise Exception('abstract class')
    def iterator(self): raise Exception('iterator() must be implemented')

class Bookshelf(__Aggregator):
    def __init__(self): self.__books = []
    def getBookAt(self, index): return self.__books[index]
    def appendBook(self, book): self.__books.append(book)
    def getLength(self): return len(self.__books)
    def iterator(self): return BookshelfIterator(self)

class Book:
    def __init__(self, name): self.__name = name
    def getName(self): return self.__name

class BookshelfIterator():
    def __init__(self, bookshelf):
        self.__bookshelf = bookshelf
        self.__index = 0

    # 以下の2関数を実装することがIteratorを実装することと等価
    def __iter__(self): return self
    def next(self):
        if self.__index >= self.__bookshelf.getLength(): raise StopIteration
        book = self.__bookshelf.getBookAt(self.__index)
        self.__index += 1
        return book


def main():
    bookshelf = Bookshelf()
    bookshelf.appendBook(Book("Around the World in 80 Days"))
    bookshelf.appendBook(Book("Bible"))
    bookshelf.appendBook(Book("Cinderella"))
    bookshelf.appendBook(Book("Daddy-Long-Legs"))
    it = bookshelf.iterator()
    for b in it: print b.getName()

if __name__=='__main__': main()
ごちそうさまでした。

design pattern



これを読んでデザインパターンを勉強しようと思います。目標は1日1章