同じタスクを実行するメニュー項目とボタンがあるとします。タスクのコードを 1 つのコントロールのアクション イベントに入れ、別のコントロールからそのイベントを呼び出すのはなぜ悪い習慣なのでしょうか? Delphi では vb6 と同様にこれを許可していますが、realbasic では許可しておらず、メニューとボタンの両方によって呼び出されるメソッドにコードを配置する必要があると述べています。
9 に答える
それはあなたのプログラムがどのように構成されているかの問題です。説明したシナリオでは、メニュー項目の動作はボタンの観点から定義されます。
procedure TJbForm.MenuItem1Click(Sender: TObject);
begin
// Three different ways to write this, with subtly different
// ways to interpret it:
Button1Click(Sender);
// 1. "Call some other function. The name suggests it's the
// function that also handles button clicks."
Button1.OnClick(Sender);
// 2. "Call whatever method we call when the button gets clicked."
// (And hope the property isn't nil!)
Button1.Click;
// 3. "Pretend the button was clicked."
end;
これら 3 つの実装はどれも機能しますが、メニュー項目がボタンに依存する必要があるのはなぜでしょうか? メニュー項目を定義するボタンの特別な点は何ですか? 新しい UI デザインでボタンがなくなったら、メニューはどうなりますか? より良い方法は、イベント ハンドラーのアクションを除外して、関連付けられているコントロールから独立させることです。これを行うにはいくつかの方法があります。
1 つは、メソッドを完全に取り除き、メソッドをイベント プロパティ
MenuItem1Click
に割り当てることです。メニュー項目のイベントに割り当てられたボタンにメソッドの名前を付けると混乱するので、イベント ハンドラの名前を変更する必要がありますが、VB とは異なり、Delphi のメソッド名は処理するイベントを定義しないため、問題ありません。署名が一致する限り、任意のメソッドを任意のイベント ハンドラーに割り当てることができます。どちらのコンポーネントのイベントも typeであるため、単一の実装を共有できます。メソッドに名前を付けるのは、メソッドが何に属しているかではなく、何をするかです。Button1Click
MenuItem1.OnClick
OnClick
TNotifyEvent
もう 1 つの方法は、ボタンのイベント ハンドラー コードを別のメソッドに移動し、両方のコンポーネントのイベント ハンドラーからそのメソッドを呼び出すことです。
procedure HandleClick; begin // Do something. end; procedure TJbForm.Button1Click(Sender: TObject); begin HandleClick; end; procedure TJbForm.MenuItem1Click(Sender: TObject); begin HandleClick; end;
このように、実際に何かを行うコードはどちらのコンポーネントにも直接結び付けられていないため、名前を変更したり、別のコントロールに置き換えたりするなど、これらのコントロールをより簡単に変更する自由が得られます。コンポーネントからコードを分離すると、3 番目の方法に進みます。
Delphi 4 で導入されたこの
TAction
コンポーネントは、同じコマンドへの複数の UI パスがあるという、説明した状況向けに特に設計されています。(他の言語や開発環境でも同様の概念が提供されています。これは Delphi に固有のものではありません。)イベント処理コードをTAction
のOnExecute
イベント ハンドラに配置し、そのアクションをAction
ボタンとメニュー項目の両方のプロパティに割り当てます。procedure TJbForm.Action1Click(Sender: TObject); begin // Do something // (Depending on how closely this event's behavior is tied to // manipulating the rest of the UI controls, it might make // sense to keep the HandleClick function I mentioned above.) end;
ボタンのように機能する別の UI 要素を追加したいですか? 問題ない。それを追加し、その
Action
プロパティを設定すれば完了です。新しいコントロールの外観と動作を古いコントロールのようにするために、さらにコードを記述する必要はありません。あなたはすでにそのコードを一度書いています。TAction
イベントハンドラだけではありません。これにより、キャプション、ヒント、可視性、有効性、アイコンなど、UI コントロールのプロパティ設定が統一されます。その時点でコマンドが有効でない場合は、Enabled
それに応じてアクションのプロパティを設定すると、リンクされたコントロールは自動的に無効になります。たとえば、コマンドがツールバーから無効になっていても、メニューから有効になっていることを心配する必要はありません。アクションのイベントを使用して、アクションが現在の条件に基づいて更新されるようにすることもできます。その代わりに、プロパティをすぐOnUpdate
に設定する必要がある何かが発生するたびに知る必要はありません。Enabled
内部ロジックを他の関数に分けて、この関数を呼び出す必要があるため...
- 両方のイベント ハンドラから
- 必要に応じてコードとは別に
これはより洗練されたソリューションであり、保守がはるかに簡単です。
ある時点でメニュー項目が意味をなさないと判断し、そのメニュー項目を削除したいとします。メニュー項目のイベント ハンドラーを指す別のコントロールが 1 つしかない場合、それは大きな問題ではないかもしれません。コードをボタンのイベント ハンドラーにコピーするだけです。しかし、コードを呼び出す方法が複数ある場合は、多くの変更を行う必要があります。
個人的には、Qt がこれを処理する方法が気に入っています。フックできる独自のイベント ハンドラーを持つ QAction クラスがあり、QAction はそのタスクを実行する必要がある任意の UI 要素に関連付けられます。
もう1つの大きな理由は、テスト容易性です。イベント処理コードがUIに埋め込まれている場合、これをテストする唯一の方法は、手動テストまたはUIに密接に関連する自動テストのいずれかを使用することです。(例:メニューAを開き、ボタンBをクリックします)。UIを変更すると、当然、数十のテストが中断される可能性があります。
コードを、実行する必要のあるジョブのみを処理するモジュールにリファクタリングすると、テストが非常に簡単になります。
関心事の分離。 クラスのプライベート イベントは、そのクラス内にカプセル化する必要があり、外部クラスから呼び出されないようにする必要があります。これにより、オブジェクト間に強力なインターフェイスがあり、複数のエントリ ポイントの発生が最小限に抑えられている場合、プロジェクトを後で簡単に変更できます。
明らかにきれいです。もちろん、使いやすさと生産性も常に重要です。
Delphi では、本格的なアプリでは一般的に控えていますが、小さなものではイベント ハンドラを呼び出します。小さなものが何らかの形で大きなものに変わった場合は、それをクリーンアップし、通常は同時にロジックと UI の分離を増やします。
ただし、Lazarus/Delphi では問題にならないことはわかっています。他の言語では、イベント ハンドラーに関連付けられた、より特別な動作が行われる場合があります。
なぜ悪い習慣なのですか?コードが UI コントロールに埋め込まれていない場合は、コードの再利用がはるかに簡単になるためです。
REALbasic でできないのはなぜですか? 技術的な理由があるとは思えません。それはおそらく彼らが下した設計上の決定にすぎません。確かに、より良いコーディング プラクティスが強制されます。
ある時点で、メニューで少し異なることを行う必要があると判断したとします。おそらく、この新しい変更は特定の状況下でのみ発生します。ボタンのことは忘れましたが、その動作も変更されました。
一方、関数を呼び出した場合、あなた (または次の人) はこれが悪い結果をもたらすことを知っているため、関数の動作を変更する可能性は低くなります。