Pythonで学ぶデザインパターン入門Builderパターン
結城先生の Java言語で学ぶデザインパターン入門
のコードを Python に書き直していくシリーズ第7段です。
Builderパターンとは
具体的な処理の骨組みを先につくっておくというパターンです。
Template Methodパターンでは、スーパークラスがサブクラスをコントロールしてましたが、このBuilderパターンでは他クラスでコントロールします。
ソースコード
コードの概要
文書を作成するプログラムを作成します。
文書を構成するためには、 Builder
クラスを継承します。 Builder
クラスを継承したクラスを、 Director
クラスがコントロールして文書を具体的に作るといったものになります。
ソースコード
Builder
# coding: utf-8 from abc import ABCMeta, abstractmethod class Builder(metaclass=ABCMeta): @abstractmethod def make_title(self, title: str): pass @abstractmethod def make_string(self, string: str): pass @abstractmethod def make_items(self, items: list): pass @abstractmethod def close(self): pass
文書を作成するための抽象クラスです。
後に紹介する Direcctor
クラスにコントロールされています。
Director
# coding: utf-8 from Builder.builder import Builder class Director(object): def __init__(self, builder: Builder): self.__builder = builder def construct(self): self.__builder.make_title('Greeting') self.__builder.make_string('朝から昼にかけて') self.__builder.make_items([ 'おはようございます。', 'こんにちは。' ]) self.__builder.make_string('夜に') self.__builder.make_items([ 'こんばんは。', 'おやすみなさい。', 'さようなら。' ]) self.__builder.close()
Builder
を継承したクラスをコントロールして文書を作成します。
construct
で文書を作成します。
TextBuilder(Builderを継承)
# coding: utf-8 from Builder.builder import Builder class TextBuilder(Builder): def __init__(self): self.__buffer = '' def make_title(self, title: str): self.__buffer += '================================\n' self.__buffer += '「%s」\n' % title self.__buffer += '\n' def make_string(self, string: str): self.__buffer += '■ %s \n' % string self.__buffer += '\n' def make_items(self, items: list): for i in items: self.__buffer += '・%s \n' % i self.__buffer += '\n' def close(self): self.__buffer += '================================\n' def get_result(self) -> str: return self.__buffer
テキストの文書を作成するクラスです。
最終的には文字列として文書が出力されます。
HTMLBuilder(Builderを継承)
# coding: utf-8 from Builder.builder import Builder class HTMLBuilder(Builder): def __init__(self): self.__filename = None def make_title(self, title: str): self.__filename = '%s.html' % title with open(self.__filename, 'a') as f: f.write('<html><head><title>%s</title></head><body>' % title) f.write('<h1>%s</h1>' % title) def make_string(self, string: str): with open(self.__filename, 'a') as f: f.write('<p>%s</p>' % string) def make_items(self, items: list): with open(self.__filename, 'a') as f: f.write('<ul>') for i in items: f.write('<li>%s</li>' % i) f.write('</ul>') def close(self): with open(self.__filename, 'a') as f: f.write('</body></html>') def get_result(self) -> str: return self.__filename
HTMLファイルとして文書が作成されるクラスです。
最終的にはファイル名が出力されます。
実行ファイル
# coding: utf-8 import sys from Builder.html_builder import HTMLBuilder from Builder.text_builder import TextBuilder from Builder.director import Director def usage(): print('python main.py plain プレーンテキストで文書作成') print('python main.py html HTMLファイルで文書作成') if __name__ == '__main__': if len(sys.argv) != 2: usage() exit() if sys.argv[1] == 'plain': html_builder = TextBuilder() director = Director(html_builder) director.construct() result = html_builder.get_result() print(result) elif sys.argv[1] == 'html': html_builder = HTMLBuilder() director = Director(html_builder) director.construct() filename = html_builder.get_result() print(filename) else: usage() exit()
実行結果
python Builder/main.py html Greeting.html python Builder/main.py plain ================================ 「Greeting」 ■ 朝から昼にかけて ・おはようございます。 ・こんにちは。 ■ 夜に ・こんばんは。 ・おやすみなさい。 ・さようなら。 ================================
実際どう便利か
Builderパターンを利用する時の便利な点は、Directorクラスは基本的に何も知らなくて良いことです。
Directorクラスは何も知らなくて良いのですが、ただどうやって文書を作成するかの方法だけを指定する必要があります。
実際、 Builderクラスを継承したインスタンスであればなんでもDirectorクラスは受け入れます。
Builderを継承したクラスを入れ替えることができるので、いろいろなBuilderクラスを構築して文書を作成することができます。
ドラゴンクエストXを支える技術を読んでみた
ドラゴンクエストXを支える技術を読んでみたので紹介します。
ざっくりどんな本か
- ドラゴンクエストXがどのように作られたかを紹介している本
- 技術の知識があまりなくてもよくわかる
といった内容の本です。
良かった点
この本の一番良かったところは、 読み物としてとてつもなく面白かったこと
です。
ゲームの中でみかける技術を素人にもわかりやすいように噛み砕いて説明してあるので、誰もが理解することのできる本になっています。
思ったより強引な方法で仕組みができているんだな?みたいなことがわかるのはとても面白かったです。
悩ましかった点
個人的な意見なのですが、 WEBアプリを作成するエンジニアにはそこまで役に立つ知識はなかったかもなと感じました。
読み物としても面白いし、3Dの知識やドラゴンクエストのニッチな機能の仕組みなどが学べたりするのですが、正直WEBエンジニアとしてのキャリアパスで利用するかなぁ・・・というものが多かった印象です。
こういう人に向いている
この本はかなりの人が楽しめる本かなぁと思います。是非皆さんに読んで欲しいおすすめの一冊ですね。
ただ、知識を得たいという方は確実にその知識にピンポイントな本を読んだほうが目的にはかないそうです。
インフラエンジニアの教科書2を読んでみた
以前読んだインフラエンジニアの教科書の続きを読みました。
公式サイトはこちら
ざっくりどんな本か
- 通信などインフラの基礎の仕組み
が記載されています。
各項目は各項目の専門書ほど詳しく書かれていないのですが、仕組みを知るには十分すぎるほどの記載があります。
良かった点
世の中のエンジニアがふんわりしか理解してないであろう箇所がかなり詳細に記載されているので非常に勉強になりました。
メモリの仕組みなど、皆さん知っているつもりでもかなりあやふやという事はあるのではないでしょうか?そんな人に突き刺さる本でした。
この本の中身全部把握したら相当なインフラの知識がついたエンジニアになれるのではないでしょうか?そんなレベルの知識が詰め込まれていました。
とても現場寄りの本で、Linuxコマンドで具体的に出力などを紹介しながらの説明が非常にわかりやすかったです。
悩ましい点
特にないですね。文字と説明が多いので文字が嫌いな人はしんどいかもです。
こういう人に向いている
正直全エンジニアに読んで欲しい本です。
いままで出会った中でも、この知識をちゃんと網羅している方には出会ったことがないです。
インフラ専門のエンジニアの方も実はRDBの中身の理解があやふやだったりします。
そんな方にも対応している本で、本当に幅広い知識がつく本です。
Pythonでsshtunnelをつかって本番DBにトンネリング接続を試してみる
パフォーマンステストをする際に開発用のDBじゃ負荷がわかりづらいみたいなことはありませんか?
そんな時にもう開発環境から本番までパフォーマンス測定のためにつなげてみよう!ってなったのでそのやり方を紹介します。
sshtunnel
sshトンネルの用途だったら様々な箇所で有用。
実際のコード
flaskでつなげたのですが、ちょうどdbのsshtunnelをしている箇所だけ抜き出しました。
# coding: utf-8 import sshtunnel ... ... tunnel = sshtunnel.SSHTunnelForwarder( ssh_address_or_host='server_address', ssh_username='username', ssh_port=port_num, remote_bind_address=( '踏み台サーバからのアドレス', ポート番号 ) ) tunnel.start() app.config['DBのURLを入れるためのconfigのkey'] = 'mysql+pymysql://user:pass@127.0.0.1:{}/dbname?charset=utf8mb4'.format(tunnel.local_bind_port) ... ...
sshtunnel.SSHTunnelForwarder
の引数でパスワード設定などもできるので足りない設定があったらいろいろ付け足しましょう。
本番につなげる場合は、できるだけテスト項目をつくりこんでさっと終わらせましょうね。
Pythonで学ぶデザインパターン入門Prototypeパターン
結城先生の Java言語で学ぶデザインパターン入門
のコードを Python に書き直していくシリーズ第6段です。
Prototypeパターンとは
今回もインスタンス生成に関するパターンです。
ざっくり説明すると、インスタンスの雛形をどこかに保持しておき、それを元に新しいインスタンスを作成するパターンですね。
コピーのような形で作成するのがこのデザインパターンの特徴です。
ソースコード
コードの概要
文字列を渡すと、特定の装飾をした文字列が出力されるといったコードになります。
たとえば、 Hello
と渡したら、 *Hello*
のように装飾されるといったコードです。
例の *
のような装飾の文字が可変になる仕組みになっております。
今回は Manager
というクラスで、 Product
というクラスを継承したインスタンスをcloneしていきます。
つまり、 Manager
にインスタンスを保持しておけば好きな時にcloneできるといった仕組みです。
ソースコード
Product
# coding: utf-8 from abc import ABCMeta, abstractmethod class Product(metaclass=ABCMeta): @abstractmethod def use(self, s: str): pass @abstractmethod def create_clone(self): pass
このデザインパターンで利用する、cloneするインスタンスの抽象クラスです。
後術の Manager
クラスで create_clone
メソッドを呼び出して複製します。
Manager
# coding: utf-8 from prototype.framework.product import Product class Manager(object): def __init__(self): self.__showcase = {} def register(self, name: str, proto: Product): self.__showcase[name] = proto def create(self, protoname: str) -> Product: p = self.__showcase.get(protoname) return p.create_clone()
具体的にインスタンスを複製するクラスです。
登録したら、そのインスタンスの複製を作成できるようになります。
MassageBox (Productを継承)
# coding: utf-8 import copy from prototype.framework.product import Product class MessageBox(Product): def __init__(self, decochar: str): if len(decochar) > 1: raise TypeError('文字は一文字') self.__decochar = decochar def use(self, s: str): length = len(s.encode('utf-8')) for i in range(length + 4): print(self.__decochar, end='') print('') print('%s %s %s' % (self.__decochar, s , self.__decochar)) for i in range(length + 4): print(self.__decochar, end='') print('') def create_clone(self) -> Product: return copy.copy(self)
複製するためのインスタンスです。具体的には
*************** * warning box * ***************
のような形で文字列を4方から取り囲む形に出力されます。
UnderlinePen (Productを継承)
# coding: utf-8 import copy from prototype.framework.product import Product class UnderlinePen(Product): def __init__(self, ulchar): if len(ulchar) > 1: raise TypeError('文字は一文字') self.__ulchar = ulchar def use(self, s: str): length = len(s.encode('utf-8')) print('%s %s %s' % (self.__ulchar, s, self.__ulchar)) print(' ', end='') for i in range(length): print(self.__ulchar, end='') print('') def create_clone(self) -> Product: return copy.copy(self)
複製するためのインスタンス2です。具体的には
~ Hello World ~ ~~~~~~~~~~~
のように下線のような形で文字列を装飾します。
実行ファイル
# coding: utf-8 from prototype.framework.manager import Manager from prototype.message_box import MessageBox from prototype.underline_pen import UnderlinePen if __name__ == '__main__': manager = Manager() upen = UnderlinePen('~') mbox = MessageBox('*') sbox = MessageBox('/') manager.register('strong message', upen) manager.register('warning box', mbox) manager.register('slash box', sbox) p1 = manager.create('strong message') p1.use('Hello World') p2 = manager.create('warning box') p2.use('warning box') p3 = manager.create('slash box') p3.use('Hello World')
実行結果
~ Hello World ~ ~~~~~~~~~~~ *************** * warning box * *************** /////////////// / Hello World / ///////////////
実際どう便利か
このデザインパターンは使い所が非常に難しいです。
インスタンスを鬼のように作成して、同じ動作でインスタンスを沢山作成する場合は非常に使えるんではないでしょうか。
また、インスタンスを複製したいけど、マニュアル動作などから生成してるので複製が難しいといった場合も有用ですね。
Pythonでlistをsetで重複排除してソート
Pythonでsetを使う際にいろいろ困ったのでこちらに備忘録を残します。
setとは?
集合を扱うための仕組み。
公式ドキュメントは以下です。
特徴としては、list
みたいな感じだけど重複除去されているということです。
item = set() item.add(1) item.add(2) item.add(3) item -> {1, 2, 3} item.add(1) item -> {1, 2, 3}
また、普通の set
はミュータブルですが、イミュータブルな frozenset
も用意されています。
listの重複を排除
さて、発端としては僕がlistの重複を排除しようとしたことです。
さて、実行してみましょう。
set(["あ", "い", "う", "え", "お", "あ"]) -> {'い', 'お', 'う', 'え', 'あ'}
見事重複が排除されました!
しかしよく見ると、順番がぐちゃぐちゃです。
そうなんです。listをsetにキャストしても順番が保証されません。
順番を維持するにはsortedを利用
Pythonのiterableなオブジェクトの並び替えに sorted
が使えます。
どうやら、この際に key
としてlistのindexを指定できれば良さそうです。
listのindexを取得するには、list.index()
というものが使えそうです。
つまり、最終的には以下のようにすればOKです。
hiragana = ["あ", "い", "う", "え", "お", "あ"] sorted(set(hiragana), key=hiragana.index) -> ['あ', 'い', 'う', 'え', 'お']
これで重複を排除しつつlistの順番を維持できました。
Pythonで学ぶデザインパターン入門 Singletonパターン
結城先生の Java言語で学ぶデザインパターン入門
のコードを Python に書き直していくシリーズ第5段です。
Singletonパターンとは
定義した範囲内で、そのものが1つしか存在しないことを保証するパターンです。
よくある例でいうと、実行環境内でインスタンス1個以上作られないことを保証するといったことをよくやります。
具体的には、クラスAのインスタンスAを作った後に、もう一度クラスAからインスタンスを生成したらインスタンスAがそのまま返されるという形のものです。
普通なら、別のインスタンスが返りますが、シングルトンパターンの場合は絶対にそのインスタンスを1個以上存在させることはしません。
ソースコード
コードの概要
パターン説明の際に紹介した具体例を実装します。
常にインスタンスが1個しか作られないことを保証するといった動作のものです。
こういった仕組みは言語特有の機能を利用するものが多く、 Java言語で学ぶデザインパターン入門
はJava特有の機能を利用したものでした。
なので今回に関しては本のコードを写経するわけではなく、 Python
特有のシングルトンパターンの実装を紹介します。
ソースコード
Singleton クラス
# coding: utf-8 import threading class Singleton(object): _instance = None _lock = threading.Lock() def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def __init__(self): print('initメソッドは毎回呼ばれるので注意')
Pythonでいうコンストラクタは __init__
ですが、インスタンス生成時に __new__
というメソッドが呼び出されます。
今回はそれを上書きして、クラス変数を利用して必ず1個しかインスタンスを保持しないような形にしました。
cls._instance
がない場合だけ、スーパークラスにもともと存在しているインスタンス生成メソッドを呼び出す形です。
しかし、もともとのコンストラクタの __init__
は毎回呼び出されてしまうことに注意してください。
_lock = threading.Lock()
は、マルチスレッドを考慮した制限になります。
実行ファイル
# coding: utf-8 from Singleton.singleton import Singleton if __name__ == '__main__': s1 = Singleton() s2 = Singleton() if s1 is s2: print('Singleton') else: print('Not Singleton')
Singleton
と表示されたらOKですね。
シングルトンパターンじゃなかったら別のインスタンスになるので Not Singleton
と表示されてしまいます。
実際どう便利か
このパターンはいろいろなところで利用することが想定されるパターンです。
たとえば、お金の計算処理などは同じデータに2回も処理が入ってはいけません。
そのようなケースは実際には多々に存在します。
むしろインスタンスを1個だけに限定するパターンは紹介には使われますが実際にはそこまで利用されることはないかもしれませんね。