linuxBean14.04(172)Observerパターン:Subjectを実装しない方法

まず「Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本」の2章のJavaの例observerを見てからPythonの例observer.pyをやって、The Lack of_ Design Patterns in Python Presentation.pdfを参考に新たに実装を考えます。

前の関連記事:linuxBean14.04(171)デザインパターン:コンポジションでstrategy_headfirst.pyを書き直す


Head-First-Design-Patternsプロジェクトのエラーをすべて解決する


linuxBean14.04(168)NetBeans8.0.2でJavaソースコードからUML図を出力するで作成したNetBeansプロジェクトの中を見るとheadfirst.designpatterns.observer.swing、headfirst.designpatterns.observer.weather、headfirst.designpatterns.observer.weatherobservableの3つのパッケージがあります。

SwingObserverExample.javaの24行目と27行目にエラーが出ています。


linuxBean14.04(25)NetBeans8とLibreOffice5のインストールのNetBeans8.0.2のJDKをOpenJDK7からOpenJDK8に変更します。

~/netbeans-8.0.2/etc/netbeans.confのnetbeans_jdkhomeを以下のように書き換えました。
#netbeans_jdkhome="/usr/lib/jvm/java-7-oracle"
#netbeans_jdkhome="/usr/lib/jvm/java-7-openjdk-i386"
netbeans_jdkhome="/usr/lib/jvm/java-8-openjdk-i386"
OpenJDK8はlinuxBean14.04(104)OpenJDK8でPyCharm2016.1を起動するでインストール済です。

これでNetBeans8.0.2を再起動しました。

これだけではエラーは消えずプロジェクトのプロパティを変更しないといけません。

Head-First-Design-Patternsプロジェクトを右クリック→プロパティ。

カテゴリからソースを選択し、ソース/バイナリ形式をJDK7からJDK8に変更してOK。

これでエラーが消えました。

あと一つエラーがでているパッケージがあって中を見てみるとDJViewServlet.javaのjavax.servletがないとのことでした。

これは11章Compoundパターンの例です。

p481の「マニア向け情報」に解説がありました。

これはTomcatが同梱されていないJavaSE版NetBeansを使っているのが原因でした。

ツール→プラグイン→使用可能なプラグイン、でtomcatで検索するとJava EEベースとうプラグインがでてきてこれをインストールしました。

NetBeansを再起動したらHead-First-Design-Patternsプロジェクトのライブラリフォルダを右クリック→ライブラリの追加。

Java EE Web 6 APIライブラリを選択して追加しました。

これでHead-First-Design-Patternsプロジェクト内のエラーがすべて消えました。

本の解説とheadfirst.designpatterns.observerとの対応


headfirst.designpatterns.observer.weather

このパッケージが2章 Observerパターンで解説されている例です。

headfirst.designpatterns.observer.weatherobservableはp64から始まるJavaの組み込みObserverパターンを使用する例です。

headfirst.designpatterns.observer.swingはp72のObserverパターンを使用しているJDKの他の機構、の例です。

これを実行するとSwingのウィンドウが表示されて、その中をクリックするたびにコンソールにテキストが出力されました。

 今回はheadfirst.designpatterns.observer.weatherobservableを使うことにします。

headfirst.designpatterns.observer.weatherからクラス図を出力する


linuxBean14.04(168)NetBeans8.0.2でJavaソースコードからUML図を出力するのeasyUMLでJavaのソースコードからクラス図を出力しました。


なぜかCurrentConditionsDisplayクラスだけがコンストラクタがの引数の型がWeatherDataではなく、Subjectになっているので集約の枝が他のObserverと違ってSubjectインターフェイスと繋がっています。

本のp56の図ではWeatherDataと繋がっているのできっとどちらでもよいのでしょう。

ObserverパターンはSubjectが状態が変化したときにObserverに通知がいく仕組みです。

Subjectの実装がWeatherData、Observerの実装がCurrentConditionsDisplay、ForecastDisplay、StatisticsDisplay、HeatIndexDisplayになっています。

WeaterStationクラスとWeatherStationHeatIndexクラスが実行例で、この実行例からSubjectであるWeaterDateのsetMeasurements()メソッドを呼び出してデータを設定しています。

WeaterDateではsetMeasurements()メソッドの最後にmeasurementsChanged()メソッドを呼び出してnotifyObservers()メソッドでObserverに通知しています。

Observerがそのdisplay()メソッドで結果を出力します。

PythonのObserverパターンの例


python-patterns/observer.pyをpyreverseでクラス図に出力します。


DataクラスがSubjectで、DecimalViewerとHexViewerがObserverです。

先ほどのJavaの例ではSubjectへの登録は、Observerのクラスをインスタンス化するときにしていますが、Pythonの例では、実行例のコードでSubjectへの登録をしています。

Observerパターンとは関係ありませんが、Dataクラスでは組み込みのproperty()クラスを使ってdata属性をデータデスクリプタにしています(linuxBean14.04(170)Pythonのデスクリプタの学習を少し参照)。

これはSubjectにデータを設定するための設定です。

Python Is Not Java (dirtSimple.org)

2004年の記事ですが、重くなるからsetter()/getter()はproperty()を使ったとしてもなるべく使わないほうがよいと書いてあります。

Subjectを実装しない方法


design-patterns.pdfの22枚目にはObserverパターンではSubjectクラスに実装は不要と書いてあります。

The Lack of_ Design Patterns in Python Presentation.pdfの30枚目を見ると確かにObserverのメソッドをデコレーターで修飾しているだけです。

この例ではPointクラスの属性scaleにあるメソッドを置換しているので、linuxBean14.04(170)Pythonのデスクリプタの学習を少しで見たようにtypes.MethodType()は不要ですが、Pointクラスのインスタンス全てに影響します。

またregisterObserver()とnotifyObservers()は実装していますが、removeObserver()は実装されていません。

python-patterns/observer_headfirst.py at 063d42c38b2c909e1ad64915001c4d1992b413ab · p--q/python-patterns

とりあえずSubjectのメソッドをObserver側で置換して、そのメソッドをSubjectで実行されたときにObserverのメソッドも実行できています。

しかし、Observeを止める方法はありません。

ということでもう少し工夫が必要です。
import types        
class Observe:  # Observer Broker
    def __init__(self,subject,subject_m_name):
        self._observer_ms = list()  # the list of Observer methods
        self.subject = subject  # the instance of the Subject
        self.subject_m_name = subject_m_name  # the name of the Subject method
        self.subject_m = getattr(self.subject, self.subject_m_name)  # the method object of the Subject method
    def register(self, observer_m):
        if observer_m not in self._observer_ms:
            self._observer_ms.append(observer_m)
        self._observe()  # Replace the Subject method    
    def _decorator(self,f):
        def g(this,*args,**kwargs):
            return self._execute(this,f,*args,**kwargs)        
        return g
    def remove(self, observer_m):
        if observer_m in self._observer_ms:
            self._observer_ms.remove(observer_m)
    def _observe(self):  # Subjectのメソッドをself._decoratorでself._executeに置換する。
        setattr(self.subject, self.subject_m_name, types.MethodType(self._decorator(self.subject_m), self.subject))
    def _execute(self,this,f,*args,**kwargs):  # Subjectのメソッドと入れ替わる関数。*argsは元のSubjectのメソッドの引数。
        f(*args,**kwargs)  # 置換する前のSubjectのメソッドをまず実行。
        for observer_m in self._observer_ms:  # Observerのリストにあるメソッドをすべて実行する。
            observer_m(this)   
SubjectとObserverを仲介するクラスを作成して、そこにObserverのメソッドのリストを保持するようにしました。

これでremoveもできるようになりました。

obs = Observe(weatherData,"setMeasurements")

まずSubjectのクラスのインスタンスとObserveするSubjectのメソッドの名前を引数にしてObserveクラスをインスタンス化します。

Subjectクラスの実装は不要で、Observeするメソッドを実行したあとにObserverのメソッドが実行されます。

currentDisplay = CurrentConditionsDisplay()
obs.register(currentDisplay.update)

registerするのはObserverのメソッドです。

Observerのメソッドの引数でSubjectのオブジェクトを受け取ります。

例ではthisで受け取っています。

python-patterns/observer_headfirst.py at cb21d485f45e5282f251a1fbf2e4558164b11d27 · p--q/python-patterns


pyreverseで出力したクラス図ではクラス間の枝が表示されていませんが、ObserveクラスからWeatherDataクラス(Subject)へは集約、ObserverクラスからCurrentConditionsDisplayクラス(Observer)、ForecastDisplayクラス(Observer)、StatisticsDisplayクラス(Observer)、HeatIndexDisplayクラス(Observer)、へもそれぞれ集約、になっています。

高階関数でSubjectからObserverへ通知しているので関数型プログラミングで実装例を探し出せると思ったのですが、見つけられませんでした。

Javaの例ではWeatherDataクラスで実装しているObserveの部分をObserverクラスに抜きだしただけですが、Observeクラス内でObserverにバインディングしているのかSubjectにバインディングしているのか考えるのと引数の引き渡し方を考えるのが、ややこしかったです。

でも今後はObserverパターンにはObserveクラスを仲介すればややこしいことは考えなくて済むはず、、、

PythonでJava風にObserverパターンを書く


Java風のデザインパターンではSubjectからObserverへの操作(notifyObservers())をSubjectに実装しないといけません。

python-patterns/observer_headfirst.py at 3d2cacb6ea9e743359785b7e280f366f05475aee · p--q/python-patterns

ObserverをSubjectに登録する部分はPythonの例にあわせて実行例のコードでやっています。

先ほどのObserveクラスの実装をSubjectであるWeatherDataクラスに実装しています。

Observerのクラスとの関係は集約になりますが、これもpyreverseでは枝は表示されていません。

検索した限りはPtyhonでもこのパターンで書いてあるものばかりで、せいぜいSubjectのリストにObserverのインスタンスを登録するか、メソッドを登録するか、のバリエーションぐらいしか見つけられませんでした。

(2017.5.20追記コンピュータの本を2冊購入で購入したPython Cookbookを読み進めていくと、PythonとJavaのデザインパターンはかなり異なることがわかりました。PythonとJavaでは関数が第一級オブジェクトかそうでないかという大きな違いがあるためです。ということで、Javaの例をPythonに訳すのはやめることにしました。)

参考にしたサイト


The Lack of_ Design Patterns in Python Presentation.pdf
Python風デザインパターンの例。

NetBeans Forums - How to add servlet library in netbeans
 javax.servletをインストールする方法

GitHub - bethrobson/Head-First-Design-Patterns: Code for Head First Design Patterns book (2014)
Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本」のJavaの例。

Python Is Not Java (dirtSimple.org)
Javaの例をそのままPythonに訳すのはよくないという説明。

Observer Observable classes in python - Stack Overflow
Python風のObserverパターンの例が提示されていますが、理解できませんでした。

ProTech | Corporate Training, Consulting & Software
Java風のPythonのObserverパターンの例。

Python Design Patterns Guide | Toptal
Python風のデザインパターンの例がありますがよく理解できないところもあります。
PR

0 件のコメント:

コメントを投稿