デザインパターン、iOS開発者の外観。 パート2.オブザーバー


内容:


パート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つのパラメーターディクショナリで行われます。


他の人に学び、学び、教えてくれてありがとう。
結局のところ、勉強している間、私たちは若いままです。 :)



Source: https://habr.com/ru/post/J324292/


All Articles