LoginSignup
0
0

More than 1 year has passed since last update.

【C#】Head First デザインパターン2章 Observerパターン

Posted at

Observerパターン

2章まで進みました。今回はObserverパターンです。1対他の構造をとるので、購読システム等を作るときに使うとよいです。観察者(Observer)が観察対象者(Subject)の変化を観察します。
良い点として挙げられているのは、疎結合です。
観察対象者が観察者がどのようなクラスであるか等を知る必要はなく、そのクラスが特定のインターフェイスを持っていることだけを知っています。
そのほかにもいろいろありますが、疎結合であるとうれしいことは一方を変更しても他方に影響がないことです。

2章では、気象台が出す気象データ(観察対象: WeatherData)を登録されているデバイス(観察者: Displays)に送信します。

インターフェイスを使わないパターン

StatisticsDisplayはここから持ってきました。
気象台が温度計等から観測したデータをすべて集め、WeatherDataにセットします。データが新しくセットされるたびに、各ディスプレイはアップデートされます。ここではWeatherDataが現在の状態と統計を表す二つのディスプレイの更新をします。

CurrentConditionsDisplayでは現在の気温と湿度を、StatisticsDisplayでは今まで受け取った結果の平均気温、最大気温、最低気温を表示します。

図はシンプルですが、クラス間に依存があります。

BadExample.cs

class Program
    {
        static void Main(string[] args)
        {
            WeatherData weatherData = new WeatherData();
            weatherData.setMeasurements(80, 65, 30.4f);
            weatherData.setMeasurements(90, 80, 60.4f);
            weatherData.setMeasurements(20, 45, 20.4f);
        }
    }


//新しくディスプレイを追加するたびにこのWeatherDataを書き換える必要がある。
    public class WeatherData
    {
        private float temperature;
        private float humidity;
        private float pressure;

        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay();

        public void measurementsChanged()
        {
            currentDisplay.update(temperature,humidity,pressure);
            statisticsDisplay.update(temperature, humidity, pressure);
            statisticsDisplay.display();
        }

        public void setMeasurements(float temperature, float humidity, float pressure)
        {
            this.temperature = temperature;
            this.humidity = humidity;
            this.pressure = pressure;
            measurementsChanged();
        }


    }

    public class CurrentConditionsDisplay
    {

        private float temperature;
        private float humidity;
        public void update(float temperature, float humidity, float pressure)
        {
            this.temperature = temperature;
            this.humidity = humidity;
            display();
        }

        public void display()
        {
            Console.WriteLine("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
        }

    }

    public class StatisticsDisplay
    {

        private float maxTemp = 0.0f;
        private float minTemp = 200;//set intial high so that minimum 
                                    //is set first invokation
        private float temperatureSum = 0.0f;
        private int numReadings = 0;       

        public int NumberOfReadings
        {
            get
            {
                return numReadings;
            }
        }

        public void update(float temperature, float humidity, float pressure)
        {
            temperatureSum += temperature;
            numReadings++;

            if (temperature > maxTemp)
            {
                maxTemp = temperature;
            }

            if (temperature < minTemp)
            {
                minTemp = temperature;
            }

        }

        public void display()
        {
            Console.WriteLine("Avg /Max/Min temperature = " + RoundFloatToString(temperatureSum / numReadings) +
                "F/" + maxTemp + "F/" + minTemp + "F");
        }

        public static string RoundFloatToString(float floatToRound)
        {
            System.Globalization.CultureInfo cultureInfo = new System.Globalization.CultureInfo("en-US");
            cultureInfo.NumberFormat.CurrencyDecimalDigits = 2;
            cultureInfo.NumberFormat.CurrencyDecimalSeparator = ".";
            return floatToRound.ToString("F", cultureInfo);
        }


    }

image.png

ここでの問題点は新しくディスプレイが増えるたびにWeatherDataを書き換えなければいけなくなることです。この密結合を疎結合にするためにインターフェイスを導入します。

図は複雑になりましたが、インターフェイスを通しているのでクラス間に依存はありません。
ディスプレイを追加したい場合、簡単にmainの中で追加することができ、追加の際にコードの変更をする必要はありません。

WithInterface.cs
class Program
    {
        static void Main(string[] args)
        {
            WeatherData weatherData = new WeatherData();

            CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);

            StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);

            weatherData.setMeasurements(80, 65, 30.4f);
            weatherData.setMeasurements(90, 80, 60.4f);
            weatherData.setMeasurements(20, 45, 20.4f);
            statisticsDisplay.display();


        }
    }

    public interface ISubject
    {
        public void registerObserver(IObserver o);
        public void removeObserver(IObserver o);
        public void notifyObservers();
    }

    public interface IObserver
    {
        public void update(float temp, float humidity, float pressure);
    }

    public interface IDisplayElement
    {
        public void display();
    }

    public class WeatherData: ISubject
    {
        private List<IObserver> observers;
        private float temperature;
        private float humidity;
        private float pressure;

        public WeatherData()
        {
            observers = new List<IObserver>();
        }

        public void registerObserver(IObserver o)
        {
            observers.Add(o);
        }

        public void removeObserver(IObserver o)
        {
            int i = observers.IndexOf(o);
            if(i>= 0)
            {
                observers.RemoveAt(i);
            }
        }

        public void notifyObservers()
        {
            for(int i = 0; i < observers.Count; i++)
            {
                IObserver observer = (IObserver)observers[i];
                observer.update(temperature, humidity, pressure);
            }
        }

        public void measurementsChanged()
        {
            notifyObservers();
        }

        public void setMeasurements(float temperature,float humidity,float pressure)
        {
            this.temperature = temperature;
            this.humidity = humidity;
            this.pressure = pressure;
            measurementsChanged();
        }
    }

    public class CurrentConditionsDisplay : IObserver, IDisplayElement
    {
        private float temperature;
        private float humidity;
        private ISubject weatherData;

        public CurrentConditionsDisplay(ISubject weatherData)
        {
            this.weatherData = weatherData;
            weatherData.registerObserver(this);
        }

        public void update(float temperature, float humidity, float pressure)
        {
            this.temperature = temperature;
            this.humidity = humidity;
            display();
        }

        public void display()
        {
            Console.WriteLine("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
        }
    }



    public class StatisticsDisplay : IObserver, IDisplayElement
    {
        #region Members
        private float maxTemp = 0.0f;
        private float minTemp = 200;//set intial high so that minimum 
                                    //is set first invokation
        private float temperatureSum = 0.0f;
        private int numReadings = 0;
        private ISubject weatherData;
        #endregion//Members

        #region NumberOfReadings Property
        public int NumberOfReadings
        {
            get
            {
                return numReadings;
            }
        }
        #endregion//NumberOfReadings Property

        #region Constructor
        public StatisticsDisplay(ISubject weatherData)
        {
            this.weatherData = weatherData;
            weatherData.registerObserver(this);
        }
        #endregion//Constructor

        #region IObserver Members

        public void update(float temperature, float humidity, float pressure)
        {
            temperatureSum += temperature;
            numReadings++;

            if (temperature > maxTemp)
            {
                maxTemp = temperature;
            }

            if (temperature < minTemp)
            {
                minTemp = temperature;
            }
            //display();
        }

        #endregion

        #region IDisplayElement Members

        public void display()
        {
            Console.WriteLine("Avg /Max/Min temperature = " + RoundFloatToString(temperatureSum / numReadings) +
                "F/" + maxTemp + "F/" + minTemp + "F");
        }

        #endregion

        #region RoundFloatToString
        public static string RoundFloatToString(float floatToRound)
        {
            System.Globalization.CultureInfo cultureInfo = new System.Globalization.CultureInfo("en-US");
            cultureInfo.NumberFormat.CurrencyDecimalDigits = 2;
            cultureInfo.NumberFormat.CurrencyDecimalSeparator = ".";
            return floatToRound.ToString("F", cultureInfo);
        }
        #endregion//RoundFloatToString

    }

image.png

結果です。

一言

次回は3章です。UML図いまだに慣れません…

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0