※記事中のソースコードは擬似的なものです
※記事ではVisualStudioのWindowsフォームアプリケーションを題材にしていますが
MFCや他のGUIアプリケーション開発でも活用できるかと思います
WPFでないWindows Form Applicationでは、class MainForm : Form
のようなForm継承クラスが1つの画面を表し、
MVCで言うViewとControl…そしてModelとしての機能も、そのクラス内に集約しがちです。
例えば以下です。
※目と頭に悪いコードですので、内容を詳しく考える必要はありません
class Hoge
から取ってきたデータを
ボタンクリックにより検索するフォームのイメージ例
※Hoge.FetchAll()はDBからなんらかのデータ列を配列として取ってくるメソッドとする
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クラスのコードのうち、ロジック部分をそちらに移します。
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);
}
}
}
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メソッドの呼び出し部分までを記述します。
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つのクラス全てを載せておきます。
namespace Foo.Data {
public sealed class FooSearchObject {
public int Id { get; set; }
public string Name { get; set; }
public string Kind { get; set; }
}
}
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);
}
}
}
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);
}
}
}
}