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







他の人に学び、学び、教えてくれてありがとう。

結局のところ、勉強している間、私たちは若いままです。 :)








All Articles