
内容:
パート0。シングルトン・ロナー
パート1.戦略
パート2.オブザーバー
今日は、パターン「Observer」の「スタッフィング」を扱います。 NotificationCenter
はすでにSDKに含まれているため、iOSの世界では、このパターンを実装する緊急の必要はないとすぐに言わなければなりません。 しかし、教育目的のために、このパターンの構造と応用を完全に分析します。 さらに、自己実装はより柔軟になり、場合によってはより便利になります。
「雨が降りそうです」(c)
例として、Design Patterns(Eric and Elizabeth Freeman)の著者は、ObserverをWeather Stationの開発に適用することを提案しています。 気象ステーションと、センサーからのデータを処理してそれらを渡す
WeatherData
オブジェクトを
WeatherData
してください。 アプリケーションは、現在の気象状態の画面、統計画面、予測画面の3つの画面で構成されています。
WeatherData
がこのインターフェイスを提供していることを知っています。
// Objective-C - (double)getTemperature; - (double)getHumidity; - (double)getPressure; - (void)measurementsChanged;
// Swift func getTemperature() -> Double func getHumidity() -> Double func getPressure() -> Double func measurementsChanged()
また、 WeatherData
開発者は、気象センサーが更新されるたびに、 measurementsChanged
メソッドが呼び出されることを報告しました。
もちろん、最も簡単な解決策は、このメソッドでコードを直接記述することです。
// Objective-C - (void)measurementsChanged { double temp = [self getTemperature]; double humidity = [self getHumidity]; double pressure = [self getPressure]; [currentConditionsDisplay updateWithTemp:temp humidity:humidity andPressure:pressure]; [statisticsDisplay updateWithTemp:temp humidity:humidity andPressure:pressure]; [forecastDisplay updateWithTemp:temp humidity:humidity andPressure:pressure]; }
// Swift func measurementsChanged() { let temp = self.getTemperature() let humidity = self.getHumidity() let pressure = self.getPressure() currentConditionsDisplay.update(with: temp, humidity: humidity, and: pressure) statisticsDisplay.update(with: temp, humidity: humidity, and: pressure) forecastDisplay.update(with: temp, humidity: humidity, and: pressure) }
このアプローチはもちろん悪いです:
- 特定の実装レベルでプログラミングします。
- 将来の複雑なスケーラビリティ。
- 情報が表示されるランタイムで画面を追加/削除することは不可能です。
- ...(オプション);
したがって、この状況ではパターン「Observer」が非常に役立ちます。 このパターンの特徴について少し話しましょう。
オブザーバー。 フードの下には何がありますか?
このパターンの主な特徴は、SUBJECTと、実際にはOBSERVERSの存在です。 ご想像のとおり、接続は1対多であり、SUBJECTの状態が変化すると、そのOBSERVERSに通知されます。 一見、すべてがシンプルです。
最初に必要なのは、オブザーバーとサブジェクトのインターフェイス(プロトコル)です。
// Objective-C @protocol Observer <NSObject> - (void)updateWithTemperature:(double)temperature humidity:(double)humidity andPressure:(double)pressure; @end @protocol Subject <NSObject> - (void)registerObserver:(id<Observer>)observer; - (void)removeObserver:(id<Observer>)observer; - (void)notifyObservers; @end
// Swift protocol Observer: class { func update(with temperature: Double, humidity: Double, and pressure: Double) } protocol Subject: class { func register(observer: Observer) func remove(observer: Observer) func notifyObservers() }
次に、 WeatherData
を整理する必要があります(対応するプロトコルだけでなく、サインオンします)。
// Objective-C // WeatherData.h @interface WeatherData : NSObject <Subject> - (void)measurementsChanged; - (void)setMeasurementWithTemperature:(double)temperature humidity:(double)humidity andPressure:(double)pressure; // test method @end // WeatherData.m @interface WeatherData() @property (strong, nonatomic) NSMutableArray<Observer> *observers; @property (assign, nonatomic) double temperature; @property (assign, nonatomic) double humidity; @property (assign, nonatomic) double pressure; @end @implementation WeatherData - (instancetype)init { self = [super init]; if (self) { self.observers = [[NSMutableArray<Observer> alloc] init]; } return self; } - (void)registerObserver:(id<Observer>)observer { [self.observers addObject:observer]; } - (void)removeObserver:(id<Observer>)observer { [self.observers removeObject:observer]; } - (void)notifyObservers { for (id<Observer> observer in self.observers) { [observer updateWithTemperature:self.temperature humidity:self.humidity andPressure:self.pressure]; } } - (void)measurementsChanged { [self notifyObservers]; } - (void)setMeasurementWithTemperature:(double)temperature humidity:(double)humidity andPressure:(double)pressure { self.temperature = temperature; self.humidity = humidity; self.pressure = pressure; [self measurementsChanged]; } @end
// Swift class WeatherData: Subject { private var observers: [Observer] private var temperature: Double! private var humidity: Double! private var pressure: Double! init() { self.observers = [Observer]() } func register(observer: Observer) { self.observers.append(observer) } func remove(observer: Observer) { self.observers = self.observers.filter { $0 !== observer } } func notifyObservers() { for observer in self.observers { observer.update(with: self.temperature, humidity: self.humidity, and: self.pressure) } } func measurementsChanged() { self.notifyObservers() } func setMeasurement(with temperature: Double, humidity: Double, and pressure: Double) { // test method self.temperature = temperature self.humidity = humidity self.pressure = pressure self.measurementsChanged() } }
テストメソッドsetMeasurement
を追加して、センサーの状態の変化をシミュレートしました。
register
メソッドとremove
メソッドは、サブジェクトごとにめったに変更されないため、デフォルトの実装remove
用意しておくと便利です。 Objective-Cでは、このために追加のクラスが必要です。 ただし、最初にプロトコルの名前を変更し、これらのメソッドを削除します。
// Objective-C @protocol SubjectProtocol <NSObject> - (void)notifyObservers; @end
次に、 Subject
クラスを追加します。
// Objective-C // Subject.h @interface Subject : NSObject @property (strong, nonatomic) NSMutableArray<Observer> *observers; - (void)registerObserver:(id<Observer>)observer; - (void)removeObserver:(id<Observer>)observer; @end // Subject.m @implementation Subject - (void)registerObserver:(id<Observer>)observer { [self.observers addObject:observer]; } - (void)removeObserver:(id<Observer>)observer { [self.observers removeObject:observer]; } @end
ご覧のとおり、このクラスには2つのメソッドと、オブザーバーの配列があります。 WeatherData
クラスで、この配列をプロパティから削除し、 NSObject
ではなくSubject
から継承します。
// Objective-C @interface WeatherData : Subject <SubjectProtocol>
迅速に、プロトコル拡張のおかげで、追加のクラスは必要ありません。
Subject
プロトコルにobservers
プロパティを含めるだけです。
// Swift protocol Subject: class { var observers: [Observer] { get set } func register(observer: Observer) func remove(observer: Observer) func notifyObservers() }
また、プロトコル拡張では、 register
デフォルトの実装を記述し、メソッドをremove
ます。
// Swift extension Subject { func register(observer: Observer) { self.observers.append(observer) } func remove(observer: Observer) { self.observers = self.observers.filter {$0 !== observer } } }
受信信号
次に、アプリケーションの画面を実装する必要があります。 CurrentConditionsDisplay
1つのみを実装します。 残りの実装も同様です。
したがって、 CurrentConditionsDisplay
クラスを作成し、2つのプロパティとdisplay
メソッドを追加しdisplay
(この画面では、現在の天気状態が表示されるはずです)。
// Objective-C @interface CurrentConditionsDisplay() @property (assign, nonatomic) double temperature; @property (assign, nonatomic) double humidity; @end @implementation CurrentConditionsDisplay - (void)display { NSLog(@"Current conditions: %f degrees and %f humidity", self.temperature, self.humidity); } @end
// Swift private var temperature: Double! private var humidity: Double! func display() { print("Current conditions: \(self.temperature) degrees and \(self.humidity) humidity") }
次に、このクラスをObserver
プロトコルに「署名」して、必要なメソッドを実装する必要があります。
// Objective-C // CurrentConditionsDisplay.h @interface CurrentConditionsDisplay : NSObject <Observer> // CurrentConditionsDisplay.m - (void)updateWithTemperature:(double)temperature humidity:(double)humidity andPressure:(double)pressure { self.temperature = temperature; self.humidity = humidity; [self display]; }
// Swift class CurrentConditionsDisplay: Observer { func update(with temperature: Double, humidity: Double, and pressure: Double) { self.temperature = temperature self.humidity = humidity self.display() }
ほぼ完了。 オブザーバーをサブジェクトに登録することは残ります(初期化解除中に登録を削除することも忘れないでください)。
これには、もう1つのプロパティが必要です。
// Objective-C @property (weak, nonatomic) Subject<SubjectProtocol> *weatherData;
// Swift private weak var weatherData: Subject?
そして、初期化解除と初期化子:
// Objective-C - (instancetype)initWithSubject:(Subject<SubjectProtocol> *)subject { self = [super init]; if (self) { self.weatherData = subject; [self.weatherData registerObserver:self]; } return self; } - (void)dealloc { [self.weatherData removeObserver:self]; }
// Swift init(with subject: Subject) { self.weatherData = subject self.weatherData?.register(observer: self) } deinit { self.weatherData?.remove(observer: self) }
おわりに
Observerパターンのかなり単純な実装を作成しました。 もちろん、私たちの選択肢には欠陥がないわけではありません。 たとえば、4番目のセンサーを追加する場合、オブザーバーインターフェイスとこのインターフェイスの実装を書き換える必要があります(4番目のパラメーターをオブザーバーに配信するため)が、これは良くありません。 記事の冒頭で述べたNotificationCenter
では、この問題は存在しません。 事実、データ転送は1つのパラメーターディクショナリで行われます。
他の人に学び、学び、教えてくれてありがとう。
結局のところ、勉強している間、私たちは若いままです。 :)