LoginSignup
1
1

【C# WPF】ifやswitchの条件節をストラテジーパターンで置き換えよう

Last updated at Posted at 2024-04-20

WPF Sheep Calendar 6日目。 読んでも読まなくてもとにかくいいねをお願いします(笑)。

この記事はとても速く読めば約3分で読めるかも知れません(usohaitteinai)。

ストラテジーパターン(Stratesy pattern)とは、Interfaceを用いて型を柔軟に切り替える事で、型(クラス)に依存しない実装を行うというプログラミングパターンです
上手く使えばif文やSwich文の条件分岐を失くしたり消したりできますはい。

Github

Visual Studio 2022 C#12 .net 8.0

ターゲットバージョンを変えて欲しい場合:コメントで

 git clone https://github.com/Sheephuman/StrategyPatternSample_sheephuman.git

参考Link

PDFがダウンロード可能です
https://speakerdeck.com/satoshishibata0108/switchwen-hazeng-yasanai-strategypatan-and-factory-methodpatan-huo-yong-shu?slide=8

リファクタリングしてみたという記事
https://zenn.dev/xurenjun/articles/0e0474530841b8

用途と利点(by GPT-4)

拡張性: 新しい戦略を追加する際に既存のコードを変更する必要がありません。
保守性と再利用性: 共通のタスクを異なる方法で実行する必要がある場合に、コードの重複を避けることができます。
選択の自由: 実行時に最適な戦略を選択することができます。

strategy patternを採用する事で既存のCodeのリファクタリングを図っていきます。

Swich文をストラテジーパターンで書いてみる

外観

<Window x:Class="StrategyPatternSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:StrategyPatternSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="250" Width="400">
    <Grid>
        <StackPanel VerticalAlignment="Center">
            <RadioButton Name="radioButton1" Content="SheepPatten1" Checked="RadioButton_Checked" />
            <RadioButton Name="radioButton2" Content="SheepPatten2" Checked="RadioButton_Checked" />
            <RadioButton Name="radioButton3" Content="SheepPatten3" Checked="RadioButton_Checked" />
            <TextBlock Name="textBlockResult" Margin="10"/>
        </StackPanel>
    </Grid>
</Window>

コードビハインド

using System.Windows;
using System.Windows.Controls;

namespace StrategyPatternSample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private IStrategy _strategy = new SheepStrategyA();
        public interface IStrategy
        {
            string Execute();
        }

        private void RadioButton_Checked(object sender, RoutedEventArgs e)
        {
            var radioButton = sender as RadioButton;
            if (radioButton != null) 
            switch (radioButton.Name)
            {
                case "radioButton1":
                    _strategy = new SheepStrategyA();
                    break;
                case "radioButton2":
                    _strategy = new SheepStrategyB();
                    break;
                case "radioButton3":
                    _strategy = new SheepStrategyC();
                    break;
            }

            ExecuteStrategy();
        }

        private void ExecuteStrategy()
        {
            if (_strategy != null)
            {
                textBlockResult.Text = _strategy.Execute();
            }
        }


        public class SheepStrategyA : IStrategy
        {
            public string Execute() => "選択されたオプションひつじは1です。";
        }

        public class SheepStrategyB : IStrategy
        {
            public string Execute() => "選択されたオプションひつじは2です。";
        }

        public class SheepStrategyC : IStrategy
        {
            public string Execute() => "選択されたオプションひつじは3です。";
        }
    }
}

実行結果

 bandicam 2024-04-20 18-17-38-891_Harua.mp4.gif

冗長ですね。
これで何故実装が柔軟になるかというと、層構造(Layer構造)をCodingで作成するからだと考えています。
実装に手間がかかる代わりに可読性とメンテナンス性が上がり、変更に対して強くなるわけです(Interface層の部分を切り替えればいいため)

Swich文を Dictionaryでマッピングして置き換え

以前の実装はVoidでメソッド化しておく。
かなり短く出来ます。

部分的に抜粋

        public MainWindow()
    {
        InitializeComponent();
        InitializeStrategyMap();
    }

 //Mapping用Dictionary    
    private Dictionary<string, IStrategy> _strategyMap = new Dictionary<string, IStrategy>();

      private void InitializeStrategyMap()
  {
     //DictionaryにStratesyと名前の設定
      _strategyMap = new Dictionary<string, IStrategy>
      {
          { "radioButton1", new SheepStrategyA() },
          { "radioButton2", new SheepStrategyB() },
          { "radioButton3", new SheepStrategyC() }
      };
  }

  
        void DictionaryStratesy(object sender)
        {
            var radioButton = sender as RadioButton;
            if (radioButton != null) 
            if (_strategyMap.TryGetValue(radioButton.Name, out var strategy))
            {
                _strategy = strategy;
                ExecuteStrategy();
            }
        }

        //Call        
        private void RadioButton_Checked(object sender, RoutedEventArgs e)
        {
            DictionaryStratesy(sender);
        }

下記Code全文

Code全文
using System.Reflection;
using System.Windows;
using System.Windows.Controls;

namespace StrategyPatternSample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            InitializeStrategyMap();
        }
        private IStrategy _strategy = new SheepStrategyA(); //defaltのStratesyとして初期化
        private Dictionary<string, IStrategy> _strategyMap = new Dictionary<string, IStrategy>();


        private void InitializeStrategyMap()
        {
            _strategyMap = new Dictionary<string, IStrategy>
            {
                { "radioButton1", new SheepStrategyA() },
                { "radioButton2", new SheepStrategyB() },
                { "radioButton3", new SheepStrategyC() }
            };
        }

        void DictionaryStratesy(object sender)
        {
            var radioButton = sender as RadioButton;
            if (radioButton != null) 
            if (_strategyMap.TryGetValue(radioButton.Name, out var strategy))
            {
                _strategy = strategy;
                ExecuteStrategy();
            }
        }


        public interface IStrategy
        {
            string Execute();
        }

        private void RadioButton_Checked(object sender, RoutedEventArgs e)
        {
            DictionaryStratesy(sender);
        }


        void switchStratesy(object sender)
        {
            var radioButton = sender as RadioButton;
            if (radioButton != null)
                switch (radioButton.Name)
                {
                    case "radioButton1":
                        _strategy = new SheepStrategyA();
                        break;
                    case "radioButton2":
                        _strategy = new SheepStrategyB();
                        break;
                    case "radioButton3":
                        _strategy = new SheepStrategyC();
                        break;
                }

            ExecuteStrategy();


        }


        private void ExecuteStrategy()
        {
            if (_strategy != null)
            {
                textBlockResult.Text = _strategy.Execute();
            }
        }


        public class SheepStrategyA : IStrategy
        {
            public string Execute() => "選択されたオプションひつじは1です。";
        }

        public class SheepStrategyB : IStrategy
        {
            public string Execute() => "選択されたオプションひつじは2です。";
        }

        public class SheepStrategyC : IStrategy
        {
            public string Execute() => "選択されたオプションひつじは3です。";
        }
    }
}

如何でしたか?(如何でしたか構文)
このように構築する事で可読性が大幅に上がりますね。

if文を使った場合をStratesypatternで

内容的にはほぼ変わらないので省略

C# WPFでRadio Buttonを3つ並べたときのif文を回避したstrategyパターンを参考に貼っておきます

複雑な条件でもStratesyPatternで対応する

by GPT-4
条件は以下
条件A、条件B、条件C、条件D をストラテジーパターンを用いてシンプルに判定
CheckBoxを使用 条件A・条件B、条件B・条件C・条件D といった複雑な条件に対応

条件インターフェースの定義


public interface IConditionStrategy
{
    bool CheckCondition();
}

ABがチェックされた時のクラス

ConditionABStrategy.cs

using System.Windows.Controls;
using static ComplexStrategySample_sheephuman.ConditionContext;


namespace ComplexStrategySample_sheephuman
{
    public class ConditionABStrategy : IConditionABStrategy
    {
        private CheckBox checkBoxA;
        private CheckBox checkBoxB;

        public ConditionABStrategy(CheckBox checkBoxA, CheckBox checkBoxB)
        {
            this.checkBoxA = checkBoxA;
            this.checkBoxB = checkBoxB;
        }

        public bool CheckConditionAB()
        {
            return checkBoxA.IsChecked == true && checkBoxB.IsChecked == true;
        }
    }
}

条件B、条件C、条件Dを組み合わせるクラス

ConditionBCDStrategy.cs
using System.Windows.Controls;
using static ComplexStrategySample_sheephuman.ConditionContext;

namespace ComplexStrategySample_sheephuman
{
    public class ConditionBCDStrategy: IConditionBCDStrategy
    {
        private CheckBox checkBoxB;
        private CheckBox checkBoxC;
        private CheckBox checkBoxD;

        public ConditionBCDStrategy(CheckBox checkBoxB, CheckBox checkBoxC, CheckBox checkBoxD)
        {
            this.checkBoxB = checkBoxB;
            this.checkBoxC = checkBoxC;
            this.checkBoxD = checkBoxD;
        }

     

        public bool CheckConditionBCD()
        {
            return checkBoxB.IsChecked == true && checkBoxC.IsChecked == true && checkBoxD.IsChecked == true;
        }
    }

}


コンテキストクラス(条件に応じて切り替えるクラス)の作成


namespace ComplexStrategySample_sheephuman
{
    public class ConditionContext
    {
        private IConditionABStrategy? abstrategy ;
        private IConditionBCDStrategy? bcdstrategy;

        public ConditionContext(IConditionABStrategy strategy)
        {
            
            this.abstrategy = strategy;
        }

        public ConditionContext(IConditionBCDStrategy strategy)
        {
            this.bcdstrategy = strategy;
        }

        public interface IConditionABStrategy
        {
            bool CheckConditionAB();

        }
        public interface IConditionBCDStrategy
        {
            bool CheckConditionBCD();

        }

        public bool EvaluateBCD()
        {
            if (bcdstrategy != null)                 
                return bcdstrategy.CheckConditionBCD();
            return false;
        }

        public bool EvaluateAB()
        {
            if (abstrategy != null)
                return abstrategy.CheckConditionAB();
            return false;
        }
    }

}

コードビハインド

MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
using static ComplexStrategySample_sheephuman.ConditionContext;

namespace ComplexStrategySample_sheephuman
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

     
        public bool CheckCondition()
        {
            return CheckBoxA.IsChecked == true && CheckBoxB.IsChecked == true;
        }

        private void EvaluateButton_Click(object sender, RoutedEventArgs e)
        {
            EveluteAB();


        }
        void EveluteBCD()
        {
            // 条件BCDの戦略を選択
          IConditionBCDStrategy strategy = new ConditionBCDStrategy(CheckBoxB, CheckBoxC,CheckBoxD);
            ConditionContext context = new ConditionContext(strategy);
            if (context.EvaluateBCD())
            {
                MessageBox.Show("条件B,C,Dがチェックされました。");
            }
            else
            {
                MessageBox.Show("B,C,Dのいずれかがチェックされていません。");
            }


        }

        void EveluteAB()
        {

            // 条件ABの戦略を選択
            IConditionABStrategy strategyAB = new ConditionABStrategy(CheckBoxA, CheckBoxB);
            ConditionContext context = new ConditionContext(strategyAB);

            if (context.EvaluateAB())
            {
                MessageBox.Show("条件Aと条件Bがチェックされました。");
            }
            else
            {
                EveluteBCD();
            }
        }
    }
}

実行結果

実行方法を含む
bandicam 2024-04-20 20-18-31-953_Harua.mp4.gif

如何でしたか?

2度めの如何でしたか構文。
如何にも難しかったかも知れませんはい。

参考書籍

2022年5月初版です。
JavaですがC#でも適用できます。現代プログラミングの基礎です。

あとがき

実装が普通にめんどくさいんですが、Interfaceによる継承先を書き直す事で変更や追加に強い事は確かなので、我慢して実装しようといいますか。こういう仕組みを人力で書かないといけないから大変なんですよね。
AIを使ってもいいと思います。シンギュラリティってこの事なんでしょうか??

1
1
1

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
1
1