C# インターフェース:賞フォームの配線を理解する (Tim Corey, Lesson 09)
Tim Corey の "C# App Start To Finish" シリーズのレッスン09では、賞金フォームのワイヤーアップに焦点を当てています。 表面的には、このフォームは単純に見えます――ユーザー入力を集め、バリデートし、モデルを作成し、それを保存するだけです。 しかし Tim は、実際の複雑さはデータを保存する場所を決めることにあると説明します:データベース、テキストファイル、あるいはその両方。 Tim のビデオは、C# プログラミングのコアコンセプトであるインターフェースを導入することによって、その解決策を案内します。
この記事では、Tim の説明を通じてインターフェースについて深く掘り下げ、スケーラブルでメンテナンスしやすいアプリケーションを作成するのにどのように役立つかを理解します。
問題: データをどこに保存するか?
Tim はまず賞金フォームの目的について述べます:それは入力を受け取り、バリデートし、ストレージに保存します。 しかし彼は、トリッキーなのはデータをどこに保存するかを決めることだと警告します。 彼は、チュートリアルがこれをスキップすることが多いのは簡単ではないからであると強調しますが、彼は学習者にそれを直接取り組んでほしいと思っています。
彼は、最初は単純な解決策を試みるかもしれないことを説明します:SQL かテキストファイルを使用しているかどうかを確認し、正しい保存プロセスを実行します。 しかし Tim は、それがどのように醜く管理しにくくなるかをすぐに示します。 もしすべてのフォームがどのタイプのストレージを使用するかをチェックしなければならないなら、コードは重複し、混乱し、変更が困難になります。
醜い方法:ハードコーディングされた条件
Tim は擬似コードの例をスケッチします。 彼は、例えば usingSQL == true のようなブール値をチェックし始め、次にデータベース接続を開き、モデルを保存し、ID と共に返すかもしれないと説明します。 次に、手動で ID を生成する必要があります。これはテキストファイルでは自動的には行われないためです。
彼は、これはすぐに繰り返しになることを指摘します。複数のフォームがこのロジックを必要とし、新しいデータソース、例えば MySQL を追加するたびに、すべてのフォームを更新する必要があります。 Tim はこれを"スケーラブルでない"と呼び、"DRY"原則(繰り返しなきこと)に反することを強調します。 彼は明確に述べます:"もっと良い方法がなければならない。"
スレッドを引っ張る:良いアプローチ
Tim は彼の戦略、つまりスレッドを引っ張ることを紹介します。 彼はコードが必要とする情報やそれがどこから来るのかという点から始まります。 彼は2つの重要な質問を特定します:
私たちはどのデータソースを使用するか、どのように知ればいいのか?
2つの異なるデータソースに接続し、同じタスクを行うにはどうするか?
Tim は、実際の保存の行為だけが異なると説明します。 フォームの観点からは、それはただ単にこう言うだけでいい:"ここにモデルがあります。 保存してください。" フォームは、それが SQL に保存しているのか、テキストファイルなのかを気にするべきではありません。
解決策:グローバルコンフィギュレーション + インターフェース
ティムはグローバルな構成システムを提案します。 彼は、どのデータソースを使用するかを知るために、アプリケーションはグローバルにアクセス可能なデータを必要とし、この情報を格納するために静的クラスを使うことを提案しています。 彼は、通常グローバル変数は避けられるものの、このケースではグローバルデータが必要不可欠であると認識しています。
次に、ティムはキーとなる概念、すなわちインターフェースについて説明しています。 彼はインターフェースを契約として定義し、それを実装するクラスは特定のメソッドやプロパティを含むことを約束します。 ティムは、これによりアプリケーションがデータソースに関わらず同じメソッドを呼び出すことができると強調しています。 フォームはSQLやテキストファイルかどうか気にせず、メソッドの呼び出しだけに関心があります。
ティムは、"同じタスクを行う必要がありながら幕の裏では2つの異なる方法で実行する場合、インターフェースが必要です"と言っています。
インターフェースの作成
ティムは実践的な実装に移り、トラッカーライブラリ内でインターフェースを作成します。 彼はそれをIDataConnectionと名付け、インターフェースに"I"を付ける命名規則を説明しています。 彼はこれがインターフェースであることをはっきり識別するために重要であると強調しています。
ティムはインターフェースに1つのメソッドを追加します:
PrizeModel CreatePrize(PrizeModel model);彼はこのメソッドが契約であり、IDataConnectionを実装する任意のクラスに存在しなければならないと説明しています。 フォームはこのメソッドを呼び出し、IDを持つPrizeModelを返されることを期待します。 ティムはこれがどのようにしてフォームがストレージタイプにとらわれずにいるかを説明しています。
グローバル構成静的クラスを作成する
次に、ティムはGlobalConfigという名前の静的クラスを作成します。彼は静的クラスはインスタンス化できず、グローバルにアクセス可能であることを説明しています。 ここにアプリケーションは利用可能なデータ接続のリストを保管します。
彼はプロパティを定義します:
public static List<IDataConnection> Connections { get; private set; }ティムはこのプライベートセットの使用を説明し、クラス自身のみがリストを修正でき、アプリケーションの他の部分はそれを読み取るだけであることを示します。
それから彼はメソッドを作成します:
public static void InitializeConnections(bool database, bool textFiles)このメソッドは利用可能なデータ接続をセットアップします。 ティムはリストが複数の接続を許可することを強調しており、これはアプリケーションがSQL、テキストファイル、または両方に保存できることを意味します。
インターフェースの理解:実世界の例
ティムは学習者に、これは複雑な教材ですが達成可能であると安心させるために一時停止します。 彼はビデオを一度見た後で、コードを組み合わせて再び見ることを勧めています。
彼はインターフェースが契約であり、これを実装する任意のクラスはこの契約に従わなければならないと説明しています。 彼はIDataConnectionを実装するSQLConnectorクラスを作成してこれを示します。
クラスが作成された際、Visual Studioは契約が満たされていないと警告します。 ティムは"インターフェースの実装"を使ってCreatePrizeメソッドを自動生成する方法を示し、NotImplementedExceptionの足場とその存在理由を説明しています—これはメソッドが機能しているふりをせずにコードがコンパイルされることを可能にします。
SQLとテキストコネクタを作成する
ティムはIDataConnectionを実装するSQLConnectorクラスとTextConnectorクラスを追加します。 彼はSQLデータベースに保存することとテキストファイルに保存することが非常に異なるプロセスであると説明しつつも、両者は同じインターフェースの契約を満たしています。
彼は現在のところ簡単なサンプルの戻り値を追加し、将来的に実際の保存ロジックを実装することを思い出すためにTODOコメントを挿入します。 これにより、アプリケーションは機能し続け、レッスンを進めることができます。
最終セットアップ:グローバル構成の配線
ティムはGlobalConfigクラスに戻り、実際の接続を配線します。 彼はConnectionsリストを初期化し、それにSQLConnectorとTextConnectorのインスタンスを追加する方法を示します。
彼は、ユーザーが同時に両方のデータソースに保存したい場合があるため、if-elseではなく2つの別々のif文が必要である理由を説明します。
どこでInitializeConnectionsを呼び出すか?
ティムは、InitializeConnectionsはアプリケーションの起動時に呼び出されなければならないと説明しています。 彼はProgram.csを修正し、次のように呼び出します:
GlobalConfig.InitializeConnections(true, true);フォームを起動する前に。 これにより、接続リストがアプリケーション全体で準備され、アクセス可能であることが保証されます。
それから彼はスタートアップフォームをCreatePrizeFormに変更して、機能をすぐにテストできるようにします。
賞フォームの検証
ティムはフォームを開き、最初のタスクを説明します:4つのフィールドの検証。 彼はイベントハンドラーをクリーンに保つことを好むので、ValidateForm()というプライベートメソッドを作成します。
ティムは、このメソッドがボタンクリックだけでなくどこからでも呼び出せることを説明しています。 それはフォームが有効かどうかを示すブール値を返します。 彼は出力変数を使ったパターンを示します:
bool output = true; return output;彼は、何かが間違っているときにfalseに変更するのが容易であるため、trueで始めるのが好きだと言っています。すべてのチェックの後にtrueに設定するよりも楽です。
場所の番号を確認する
ティムは最初のバリデーションを説明します:場所の番号はゼロ以上の整数でなければなりません。
彼はint.TryParseを使用してPlaceNumberValue.Text(文字列)を整数に変換します。 ティムはTryParseがどのように機能するかを詳細に説明しています:
* 文字列を取り込み、数値に変換しようとします。
* 成功または失敗を示すブールを返します。
* 変換された値を出力するためのoutパラメタグを使用します。
ティムは、TryParseがParseより安全であることを強調します。なぜなら不適切な入力でもクラッシュせず、falseを返して出力をゼロに設定するからです。
彼はその論理を説明します:
* placeNumberValidNumberがfalseの場合、output = falseに設定します。
* placeNumberが1より小さい場合、output = falseに設定します。
ティムはここでelse文を使用しないように警告しています。なぜならこのメソッドには複数のチェックが含まれているからです。 1つのチェックで失敗しても、メソッドは他のチェックを評価し続け、すべてのエラーを収集する必要があります。
場所の名前を検証する
ティムは次のバリデーションに移ります:場所の名前は空であってはなりません。
彼は確認します:
if (placeNameValue.Text.Length == 0) { output = false; }ティムは、実際のアプリケーションでは、失敗したバリデーションごとにエラーメッセージを表示するだろうと説明しています。 しかし今のところ、彼はシンプルに維持し、true/falseだけを返します。
賞金額と賞百分率の検証
ティムは、フォームには賞金額または賞百分率のいずれかが含まれていなければならない(どちらか一方がゼロを超えている必要がある)と説明しています。 彼は重要な違いを指摘します:
* 賞百分率は整数です
* 賞金額は小数(decimal)です。なぜならお金はセントを含むことがあり得ます。
彼は変数を作成します:
decimal prizeAmount = 0; int prizePercentage = 0;それから両方に対してTryParseを使用します:
bool prizeAmountValid = decimal.TryParse(prizeAmountValue.Text, out prizeAmount); bool prizePercentageValid = int.TryParse(prizePercentageValue.Text, out prizePercentage);ティムは両方が有効な数字でなければならないと説明しています。 どちらかが無効であれば、フォームは無効です。
次に、少なくとも一方がゼロより大きいことを確認します:
if (prizeAmount <= 0 && prizePercentage <= 0) { output = false; }ティムはまた、百分率が0から100の間にあることを確認するチェックを追加します:
if (prizePercentage < 0||prizePercentage > 100) { output = false; }彼はその理由を説明します:150%はあなたが賞金プールより多くを贈与していることを意味し、それは不可能です。
バリデーション結果の使用
すべてのチェックが完了した後、ティムは結果の使用方法を説明しています:
if (ValidateForm()) { // モデルを作成して保存 } else { MessageBox.Show("このフォームには無効な情報が含まれています。 確認してもう一度試してください。"); }ティムは、最初の失敗で早期に返すことも可能ですが、彼はすべてのチェックを実行して、ユーザーがすべてのバリデーションエラーを一度に見ることができるように選択します。 これにより、彼らは一度にすべて修正することができるので、苛立ちを減らします。
PrizeModelの作成
ティムは、フォームが有効になったら次のステップはPrizeModelを作成することであると説明しています。
彼はモデルをインスタンス化する方法を示しています:
PrizeModel model = new PrizeModel(); model.PlaceName = placeNameValue.Text; model.PlaceNumber = placeNumberValue.Text; // 問題点:これは文字列ですティムは問題を指摘しています:PlaceNumberはintである一方で、フォームの値は文字列です。 彼はこの問題を解決する2つのオプションを説明しています:
* フォーム内で各値を再び解析します(反復的)。
* PrizeModelでコンストラクタオーバーロードを追加します。
ティムはオプション2を選びます。
PrizeModelでのオーバーロードされたコンストラクタ
ティムは4つの文字列を受け入れるオーバーロードされたコンストラクタを追加します:
public PrizeModel(string placeName, string placeNumber, string prizeAmount, string prizePercentage)
{
PlaceName = placeName;
PlaceNumber = int.TryParse(placeNumber, out int placeNumberValue) ? placeNumberValue : 0;
PrizeAmount = decimal.TryParse(prizeAmount, out decimal prizeAmountValue) ? prizeAmountValue : 0;
PrizePercentage = double.TryParse(prizePercentage, out double prizePercentageValue) ? prizePercentageValue : 0;
}ティムは、解析が失敗してもゼロにデフォルトするので気にしないと説明しています。これは数字のデフォルト値です。
このコンストラクタは、フォームが文字列入力で直接PrizeModelを作成し、モデルが解析を管理することを可能にします。
IDataConnectionを使用してモデルを保存する
モデルができたので、ティムはグローバルな接続リストを使用してそれを保存する方法を説明しています。
彼はforeachループを使用しています:
foreach (IDataConnection db in GlobalConfig.Connections) { db.CreatePrize(model); }
ティムはこのループが各接続(SQLとテキストファイル)でCreatePrize()を呼び出すと説明しています。 メソッドがまだ実装されていないにもかかわらず、フォームは機能しておりデータを保存しているふりをします。 これはインターフェースおよびグローバル構成パターンが動作していることを証明します。
フォームのテスト
ティムは早期テストの重要性を強調しています。 彼はブレークポイントを追加し、アプリケーションを実行します。
* 彼はまず空のフォームをテストします。
* 彼はValidateForm()をステップ実行します。
* 出力がfalseであり、バリデーションが予想通り失敗することを確認します。
* 次に有効なデータを入力し、コンストラクタがモデルを正しく設定することを確認します。
* 彼はまたループが両方の接続を通ることを確認します。
ティムはフォームが機能しておりパターンが検証されていることを示しています。
最終クリーンアップ:フォームをクリアする
ティムはいくつかの最終調整を行います:
* 賞を正常に作成した後、フォームフィールドをクリアします。
* 賞金額および百分率のデフォルト値を0に設定し、ユーザーが毎回ゼロを入力する必要がないようにします。
彼は次に、有効な提出後にフォームが正しくクリアされることを確認します。
次に何をするか?
Timはビデオを締めくくり、次のステップはSQLとテキストの接続クラスを結び付けて、実際にデータを保存することだと言っています。
彼は視聴者に次のレッスンをお見逃しなくと伝え、そこでSQLコネクタを実装し、実際にデータベースに接続することをお知らせしています。

