6.OpenCVで画像処理アプリを作ろう(シングルトン)

こんにちは、ゆたんぽです。

前回に引き続きOpenCVで画像処理アプリを作成していきます。

今回は、シングルトンについて解説していきます。

画像処理アプリを実現するために必要なデザインパターンです。


シングルトンとは

「あるクラスのインスタンスを1つしか作成しないデザインパターン」のことです。

今回作成している画像処理アプリでいうとModelで作成しているImgageProcessExeなどのクラスのインスタンスを1つしか作らないということです。

今回、画像処理のクラスは1つのViewにつき、1つのViewModel、1つのModelを紐づけて作成しています。
そのため、Modelにある画像処理のクラスも1回のインスタンス化のみでいいはずです。
なので、シングルトンで「インスタンスを1つしか作成しない」ようにすれば、毎回インスタンス化する必要がないのでコードも省略できます。

また、シングルトンはクラス間でデータを共有したい場面で利用できます。
例として、class A でシングルトンクラスのインスタンス生成、class B class A で編集したデータを使用する場合などです。


シングルトンでは、クラスでインスタンスが1つしか生成できないので、初めに作成したインスタンスを別クラスと兼用することができます。

今回作成しているアプリでは、画像とパラメータ入力、出力結果でViewを分けて表示する予定です。
画像とパラメータ入力、出力結果がそれぞれインスタンス化すると、それぞれが別のインスタンスを参照していますので関連付けができません。
そのため、画像⇔パラメータ⇔出力結果の関連付けをシングルトンで同じクラスを参照することで実現しようとしています。


シングルトン実装

それでは、実際にシングルトンのデザインパターンを実装していきましょう。

以下にプログラムを載せて説明していきます。事前にModelsフォルダにAppSettingというクラスを作成しておいてください。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OpenCV_Prism.Models
{
    class AppSetting
    {
        private static AppSetting _instance = new AppSetting();//①
     
     #region 【プロパティ】        

     public static AppSetting Instance//②
        {
            get { return _instance; }
        }

        public ImageProcessExe IPr;//③

        public ImageThreshold ITh;

        #endregion

        #region 【コンストラクタ】

        private AppSetting()//④
        {
            IPr = new ImageProcessExe();
            ITh = new ImageThreshold();
        }

        #endregion


    }
}

初めに①、②のプログラムのように、自分自身のインスタンスを作成し、プロパティで公開しています。

そのため、アクセスはプロパティである、Instanceから行います。

ポイントとしては、④のコンストラクタをprivateにしているところです。
これにより、他のクラスからAppSettingクラスはインスタンス化することができなくなります。

コンストラクタ内では、③で定義したプロパティをインスタンス化しています。

他のクラスからAppSettingクラスはインスタンス化することができないので、①で行った自身のインスタンス化でのみインスタンス化されるのです。
よって、共有したいクラス同士がpublicであるInstanceにアクセスすることで、共有的に作業することができるようになるのです。


ImageViewAreaViewModelの修正

上記で説明したシングルトンで作成したAppSettingを使用して前回作成したImageViewAreaViewModelを修正しようと思います。

using OpenCV_Prism.Abstract;
using OpenCV_Prism.Models;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Reactive.Disposables;
using System.Windows.Media.Imaging;

namespace OpenCV_Prism.ViewModels
{
    public class ImageViewAreaViewModel : BindableBase, INavigationAware
    {
        // リアクティブプロパティ破棄
        protected CompositeDisposable _disposables = new CompositeDisposable();

        ImageProcess Ipe;//①

        public ReactiveProperty<BitmapImage> Image { get; set; }

        public ImageViewAreaViewModel()
        {
            Ipe = AppSetting.Instance.IPr;//②

            ImageSelect = new DelegateCommand(ImageSelectExe);

            ImageProcess = new DelegateCommand(ImageProcessExe);

            Image = Ipe.ToReactivePropertyAsSynchronized(x => x.Image)
                .AddTo(_disposables);
        }

        public DelegateCommand ImageSelect { get; set; }

        public DelegateCommand ImageProcess{ get; set; }

        #region 【InavigateAware】

        public void OnNavigatedTo(NavigationContext navigationContext)
        {
        }

        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            return true;
        }

        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
            var screenInfo = navigationContext.Parameters.GetValue<string>("ScreenInfo");

            switch (screenInfo)
            {
                case nameof(ImageProcessExe):
                    Ipe = AppSetting.Instance.IPr;//③
                    break;

                case nameof(ImageThreshold):
                    Ipe = AppSetting.Instance.ITh;//④
                    break;

                default:
                    break;
            }

            Image = Ipe.ToReactivePropertyAsSynchronized(x => x.Image)
                        .AddTo(_disposables);

        }

        #endregion


        #region 【メソッド】
        private void ImageSelectExe()
        {
            Ipe.ImageSelect();//⑤
        }

        private void ImageProcessExe()
        {
            Ipe.myRun();//⑥
        }

        #endregion
    }
}

上記で示すようにすっきりしているのがわかると思います。

理由は、AppSettingを使用することで、画像処理のクラスをインスタンス化する必要がないこと。

もう一つは、画像処理を継承したクラスであるImageProcessをメンバーとして持ち、ImageProcessAppSettingのプロパティを割り当てるようにしているからです。これにより、ポリモフィズムを使って共通するメソッドは記述を変えずに呼び出すことができるので、無駄を省くことができました。

ImageProcessをメンバー として定義しています。

②コンストラクタで初期化する際は、AppSettingIPrを割り当てています。

③Grayボタンを押された際には、AppSetting.Instance.IPrを割り当てることでImageProcessExeを呼び出しています。

④ ③と同様にImageThresholdを呼び出しています。

⑤、⑥ ポリモフィズムを使用して、共通のメソッドを呼び出す関数にしています。

上記のように書き換えても、前回同様に起動できることを確かめてみてください。


まとめ&次回予告

今回は、シングルトンの説明を行ってきました。

シングルトンを使用することで、無駄なインスタンス化を省き、異なるクラス同士でインスタンスを共通化することができるのは大きなメリットになりますので是非覚えてください。

次回から使用するパラメータの受け渡しでも使用していきます。

次回もよろしくお願いします。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です