LoginSignup
41
43

More than 5 years have passed since last update.

Windowsフォームアプリケーションでモジュール強度を高める努力

Last updated at Posted at 2015-09-29

※記事中のソースコードは擬似的なものです
※記事ではVisualStudioのWindowsフォームアプリケーションを題材にしていますが
 MFCや他のGUIアプリケーション開発でも活用できるかと思います


 WPFでないWindows Form Applicationでは、class MainForm : FormのようなForm継承クラスが1つの画面を表し、
MVCで言うViewとControl…そしてModelとしての機能も、そのクラス内に集約しがちです。

 例えば以下です。
※目と頭に悪いコードですので、内容を詳しく考える必要はありません

 class Hogeから取ってきたデータを
ボタンクリックにより検索するフォームのイメージ例
※Hoge.FetchAll()はDBからなんらかのデータ列を配列として取ってくるメソッドとする

FooSearchForm.cs
namespace Foo {
  public class FooSearchForm : Form {
    public FooSearchForm() {
      InitializeComponents();
      this.SetDefaultDataGridViewStyle();
      this.LoadDefaultDataGridViewData();
    }

    private void SetDefaultDataGridViewStyle() {
      dgvFoo.AllowUserToAddRows       = false;
      dgvFoo.AllowUserToResizeRows    = false;
      dgvFoo.AllowUserToResizeColumns = false;
    }

    private void LoadDataGridViewData() {
      var foos = Hoge.FetchAll()
        .Where(x => x.Kind == "Neko")
        .Select(new object[] { x.Id, x.Name, x.Kind });
      foreach (var foo in foos) {
        dgvFoo.Rows.Add(foo);
      }
    }

    private void btnSearch_Clicked(object sender, EventArgs e) {
      dgvFoo.Rows.Clear();
      var foos = Hoge.FetchAll()
        .Where(x => x.Id   == int.Parse(txtId.Text)
                 && Regex.IsMatch(txtName.Text, x.Name)
                 && x.Kind == txtKind.Text)
        .Select(new object[] { x.Id, x.Name, x.Kind });
      foreach (var foo in foos) {
        dgvFoo.Rows.Add(foo);
      }
    }
  }
}

 見ての通りこのFooSearchFormクラスでは、初期化操作ロジックや検索ロジックが
直に書いてあって、ぱっと見わかりにくいです。

 この例ではコードが小さいので理解できますが、画面に様々な機能や複雑なロジックがあった場合
コード量も多くなって、貴方はきっと思わずスパゲッティを茹でてしまいたくなります。

私はこれに対して、以下の解決案を挙げます。

具体的ロジックを他のソースに分離する

FooSearchFormクラスを例に挙げます。

ここではclass FooSearchFormModuleを作成し、
FooSearchFormクラスのコードのうち、ロジック部分をそちらに移します

FooSearchForm.cs
namespace Foo {
  public class FooSearchForm : Form {
    public FooSearchForm() {
      InitializeComponents();
      dgvFoo.SetDefaultDataGridViewStyle();
      dgvFoo.LoadDefaultDataGridViewData();
    }

    private void btnSearch_Clicked(object sender, EventArgs e) {
      dgvFoo.SearchFooData(int.Parse(txtId.Text), txtName.Text, txtKind.Text);
    }
  }
}
FooSearchFormModule.cs
namespace Foo.Module {
  public static class FooSearchFormModule {
    public static void SetDefaultDataGridViewStyle(this DataGridView dgvFoo) {
      dgvFoo.AllowUserToAddRows       = false;
      dgvFoo.AllowUserToResizeRows    = false;
      dgvFoo.AllowUserToResizeColumns = false;
    }

    public static void LoadDataGridViewData(this DataGridView dgvFoo) {
      var foos = Hoge.FetchAll()
        .Where(x => x.Kind == "Neko")
        .Select(new object[] { x.Id, x.Name, x.Kind });
      foreach (var foo in foos) {
        dgvFoo.Rows.Add(foo);
      }
    }

    public static void SearchFooData(this DataGridView dgvFoo, int id, string name, string kind) {
      dgvFoo.Rows.Clear();
      var foos = Hoge.FetchAll()
        .Where(x => x.Id   == id
                 && Regex.IsMatch(name, x.Name)
                 && x.Kind == kind)
        .Select(new object[] { x.Id, x.Name, x.Kind });
      foreach (var foo in foos) {
        dgvFoo.Rows.Add(foo);
      }
    }
  }
}

 試しにFooSearchFormクラスだけを見てみてください。
ロジックを排除したおかげで、このクラスが「何をしているのか」がわかりやすくなったかと思います。

  • 大事なところ(とても)
    • FooSearchFormが処理を抽象的に示している
    • FooSearchFormModuleがFooSearchFormの処理の具体的ロジックを持っている

パラメータをオブジェクトに纏める

 FooSearchFormModule.SearchFooDataメソッドでは、FooSearchFormが受け付けた入力(txtId.Text, txtName.Text, txtKind.Text)
を引数として個別に受け取っています。

これはモジュール結合度のうちデータ結合といわれており、モジュール結合のうち最も良いとされています。 参考

 しかしこのパラメータがもし、{txtId.Text, txtName.Text, txtKind.Text}の3つ以上に増えた場合
FooSearchFormModule.SearchFooDataメソッドの引数にも列挙を追加することになります。

 こんなの見たくないですね!
ショッキングなコードを見たくない方は、暫しの間、目を閉じていてください。 HAHAHA!
dgvFoo.SearchFooData(int.Parse(txtId.Text), txtFirstName.Text, txtSecondName.Text, txtKind.Text, txtTag.Text, txtTag.Text);

 このような場合は、単一のオブジェクトにまとめてからデータのやり取りを行いましょう。
FooSearchForm.btnSearch_Clickedメソッドから
FooSearchFormModule.SearchFooDataメソッドの呼び出し部分までを記述します。

FooSearchObject.cs
namespace Foo.Data {
  public sealed class FooSearchObject {
    public int    Id   { get; set; }
    public string Name { get; set; }
    public string Kind { get; set; }
  }
}
private void btnSearch_Clicked(object sender, EventArgs e) {
  var fooSearch = new FooSearchObject() {
      Id   = int.Parse(txtId.Text)
    , Name = txtName.Text
    , Kind = txtKind.Text
  };
  dgvFoo.SearchFooData(fooSearch);
}

あとがき (あとがき)

 これらはWPFアプリケーションを少しだけ書いてみて
その後Windowsフォームアプリケーション作成に戻ってみたところ、思いついたものです。

なのでMVVMにとても影響されているかと思います。 ( されていたら、嬉しい )

 最後に、以下に最終的な3つのクラス全てを載せておきます。

FooSearchObject.cs
namespace Foo.Data {
  public sealed class FooSearchObject {
    public int    Id   { get; set; }
    public string Name { get; set; }
    public string Kind { get; set; }
  }
}
FooSearchForm.cs
namespace Foo {
  public class FooSearchForm : Form {
    public FooSearchForm() {
      InitializeComponents();
      dgvFoo.SetDefaultDataGridViewStyle();
      dgvFoo.LoadDefaultDataGridViewData();
    }

    private void btnSearch_Clicked(object sender, EventArgs e) {
      var fooSearch = new FooSearchObject() {
          Id   = int.Parse(txtId.Text)
        , Name = txtName.Text
        , Kind = txtKind.Text
      };
      dgvFoo.SearchFooData(fooSearch);
    }
  }
}
FooSearchFormModule.cs
namespace Foo.Module {
  public static class FooSearchFormModule {
    public static void SetDefaultDataGridViewStyle(this DataGridView dgvFoo) {
      dgvFoo.AllowUserToAddRows       = false;
      dgvFoo.AllowUserToResizeRows    = false;
      dgvFoo.AllowUserToResizeColumns = false;
    }

    public static void LoadDataGridViewData(this DataGridView dgvFoo) {
      var foos = Hoge.FetchAll()
        .Where(x => x.Kind == "Neko")
        .Select(new object[] { x.Id, x.Name, x.Kind });
      foreach (var foo in foos) {
        dgvFoo.Rows.Add(foo);
      }
    }

    public static void SearchFooData(this DataGridView dgvFoo, FooSearchObject fooSearch) {
      dgvFoo.Rows.Clear();
      var foos = Hoge.FetchAll()
        .Where(x => x.Id   == fooSearch.Id
                 && Regex.IsMatch(fooSearch.Name, x.Name)
                 && x.Kind == fooSearch.Kind)
        .Select(new object[] { x.Id, x.Name, x.Kind });
      foreach (var foo in foos) {
        dgvFoo.Rows.Add(foo);
      }
    }
  }
}
41
43
4

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
41
43