Java Swingクラスをいつ拡張する必要がありますか?


35

継承の実装に関する私の現在の理解では、IS-A関係が存在する場合にのみクラスを拡張する必要があります。親クラスがさらに異なる機能を持つより具体的な子タイプを持つことができるが、親で抽象化された共通要素を共有する場合。

私のJava教授が私たちにすべきだと勧めていることのために、私はその理解に疑問を抱いています。彼は、JSwing私たちがクラスで構築しているアプリケーションのために

一つは、すべての拡張する必要がありますJSwingクラス(JFrameJButtonJTextBox(部品サイズ、部品ラベルなどのような)別のカスタムクラスに、など)と、それらに関連するGUIカスタマイズを指定します

これまでのところこれでいいのですが、彼はさらに、すべてのJButtonが独自のカスタム拡張クラスを持つべきであり、唯一の識別要素はラベルであるとアドバイスしています。

たとえば、GUIに2つのボタンOkayCancelがある場合。彼は、以下のように拡張することを推奨しています。

class OkayButton extends JButton{
    MainUI mui;
    public OkayButton(MainUI mui) {
        setSize(80,60);
        setText("Okay");
        this.mui = mui;
        mui.add(this);        
    }
}

class CancelButton extends JButton{
    MainUI mui;
    public CancelButton(MainUI mui) {
        setSize(80,60);
        setText("Cancel");
        this.mui = mui;
        mui.add(this);        
    }
}

ご覧のとおり、唯一の違いはsetText機能にあります。

それでは、この標準的な慣習はありますか?

ところで、これが議論されたコースは、Javaのベストプログラミングプラクティスと呼ばれています

[教授からの返信]

そこで私は教授と問題について話し合い、答えに挙げられているすべての点を挙げました。

彼の正当性は、サブクラス化がGUI設計標準に従って再利用可能なコードを提供することです。たとえば、開発者が1つのウィンドウでカスタムボタンOkayCancelボタンを使用している場合、同じボタンを他のウィンドウにも配置する方が簡単です。

理由はわかりますが、それでも継承を利用してコードを脆弱にしているだけです。

後で、開発者はボタンを誤って呼び出して変更setTextする可能性がありOkayます。その場合、サブクラスは単に迷惑になります。


3
クラスの外部でJButton単純にを作成してJButton同じパブリックメソッドを呼び出すことができるのに、なぜコンストラクタでパブリックメソッドを拡張して呼び出すのですか?

17
可能であれば、その教授に近づかないでください。これはベストプラクティスとはほど遠いです。あなたが示したようなコードの複製は非常に悪臭です。
njzk2

7
ここで彼の動機を伝えることをheしないで、なぜ彼に尋ねてください。
アレックス

9
私はそれがひどいコードだからダウン票を投じたいが、悪い教授が言うことを盲目的に受け入れるのではなく、あなたが質問しているのが素晴らしいので、私も賛成票を投じたい。
ファンドモニカの訴訟

1
@アレックス私は教授の正当化で質問を更新しました
パラス

回答:


21

これは、a が期待されるどの場所でも置換できないため、Liskov Substitution Principleに違反してOkayButtonいますButton。たとえば、任意のボタンのラベルを好きなように変更できます。しかし、OkayButtonそれを内部の不変条件に違反して行うと。

これは、コードの再利用のための継承の古典的な誤用です。代わりにヘルパーメソッドを使用してください。

これを行わないもう1つの理由は、これが線形コードと同じことを達成する複雑な方法にすぎないことです。


これはOkayButton、あなたが考えている不変式を持たない限り、LSPに違反しません。OkayButtonそれは単にデフォルトとしてテキストを考慮し、完全にテキストに変更をサポートする予定があり、余分な不変条件を持っていない請求することができます。まだ良い考えではありませんが、そのためではありません。
-hvd

あなたができるので、それは違反しません。つまり、メソッドにactivateButton(Button btn)ボタンが必要な場合、のインスタンスを簡単に与えることができますOkayButton。原則は、継承がほとんど役に立たなくなるため、まったく同じ機能を持たなければならないという意味ではありません。
セブ

1
@Sebb。ただし、関数でそれを使用することはできませんsetText(button, "x")。これは、(想定される)不変条件に違反するためです。この特定のOkayButtonには興味深い不変式がない可能性があるので、悪い例を選んだと思います。しかし、そうかもしれません。たとえば、クリックハンドラーが言う場合はどうなりますif (myText == "OK") ProcessOK(); else ProcessCancel();か?その場合、テキストは不変式の一部です。
usr

@usrテキストが不変式の一部であるとは考えませんでした。あなたは正しい、それから違反している。
Sebb

50

それはあらゆる可能な方法で完全にひどいです。で最も、JButtonがを生成するファクトリ関数を使用します。いくつかの深刻な拡張のニーズがある場合にのみ、それらから継承する必要があります。


20
+1。詳細については、AbstractButtonの Swingクラス階層を参照してください。次に、JToggleButtonの階層を見てください。継承は、さまざまなタイプのボタンを概念化するために使用されます。ただし、ビジネスコンセプトの差別化には使用されていませんはい、いいえ、続行、キャンセル...)。
ライヴ

2
@Laiv:さえ、例えば、異なるタイプのためを有するJButtonJCheckBoxJRadioButton、そのような種類が標準的である普通AWTのような他のツールキット、精通している開発者にだけ譲歩です。原則として、それらはすべて単一のボタンクラスで処理できます。実際の違いを生むのは、モデルとUIデリゲートの組み合わせです。
ホルガー

はい、それらは単一のボタンで処理できます。ただし、実際の階層は適切なプラクティスとして公開できます。新しいButtonを作成するか、イベントとbehaivorの制御を別のコンポーネントに委任することは、設定の問題です。もし私なら、私のowmボタンをモデル化し、その振る舞い、アクションをカプセル化するためにSwingコンポーネントを拡張します...プロジェクトでの実装を簡単にし、後輩が簡単にするためだけです。Web環境では、イベント処理が多すぎるよりもWebコンポーネントを好みます。いずれかの方法。BehaivorsからUIを切り離すことができます。
ライヴ

12

これは、Swingコードを記述する非常に非標準的な方法です。通常、Swing UIコンポーネントのサブクラスを作成することはほとんどありません。最も一般的なのはJFrame(子ウィンドウとイベントハンドラーをセットアップするため)です。しかし、そのサブクラスでさえ不要であり、多くの人が推奨しません。ボタンなどへのテキストのカスタマイズの提供は、通常、AbstractActionクラス(またはそれが提供するActionインターフェイス)を拡張することによって実行されます。これにより、テキスト、アイコン、およびその他の必要な視覚的なカスタマイズを提供し、それらを表す実際のコードにリンクできます。これは、表示する例よりもはるかに優れたUIコードの記述方法です。

(ちなみに、Google奨学生はあなたが引用した論文を聞いたことがありません。より正確な参考文献はありますか?)


「標準的な方法」とは正確には何ですか?すべてのコンポーネントのサブクラスを作成することはおそらく悪い考えですが、公式のSwingチュートリアルでは依然としてこの方法が好まれていることに同意します。教授は、フレームワークの公式ドキュメントに示されているスタイルに従うだけだと思います。
から来る

@COMEFROM Swingチュートリアル全体を読んでいなかったことを認めるので、このスタイルがどこで使用されているか見逃したかもしれませんが、私が見たページはサブクラスを使用せず、docs.oracleなどの
ジュール

@Jules混乱して申し訳ありません。教授が教えるコース/ペーパーはJavaのベストプラクティス
パラス

1
一般的に正しいですが、もう1つの大きな例外があります- JPanelJFrameパネルは単なる抽象コンテナであるため、実際にはサブクラスよりもサブクラスの方が便利です。
オーダス

10

私見、Javaのベストプログラミングプラクティスは、Joshua Blochの本「Effective Java」で定義されています。先生があなたにOOPのエクササイズを提供してくれているのは素晴らしいことです。他の人のプログラミングスタイルを読み書きすることを学ぶことが重要です。しかし、Josh Blochの本以外では、ベストプラクティスに関する意見はかなり異なります。

このクラスを拡張する場合は、継承を利用することもできます。MyButtonクラスを作成して共通コードを管理し、変数部分のサブクラスを作成します。

class MyButton extends JButton{
    protected final MainUI mui;
    public MyButton(MainUI mui, String text) {
        setSize(80,60);
        setText(text);
        this.mui = mui;
        mui.add(this);        
    }
}

class OkayButton extends MyButton{
    public OkayButton(MainUI mui) {
        super(mui, "Okay");
    }
}

class CancelButton extends MyButton{
    public CancelButton(MainUI mui) {
        super(mui, "Cancel");
    }
}

これはいつ良いアイデアですか?作成したタイプを使用するとき!たとえば、ポップアップウィンドウを作成する関数があり、署名が次の場合:

public void showPopUp(String text, JButton ok, JButton cancel)

作成したばかりのタイプは、何の役にも立ちません。しかし:

public void showPopUp(String text, OkButton ok, CancelButton cancel)

これで便利なものが作成されました。

  1. コンパイラーは、showPopUpがOkButtonとCancelButtonをとることを確認します。誰か読んコードは、それが古くなった場合のドキュメントのこの種は、コンパイル時にエラーが発生しますので、この機能を使用することを意味している方法を知っています。これは大きな利点です。型安全性の利点に関する1つまたは2つの実証研究では、定量化できる唯一の利点が人間のコードの理解であることがわかりました。

  2. これは、関数に渡すボタンの順序を逆にするエラーも防ぎます。これらのエラーを見つけることは困難ですが、非常にまれであるため、これはわずかな利点です。これはタイプセーフティを販売するために使用されましたが、特に有用であることは経験的に証明されていません。

  3. 関数の最初の形式は、任意の2つのボタンを表示するため、より柔軟です。場合によっては、どの種類のボタンを使用するかを制限するよりも良い場合があります。任意のボタン機能をより多くの状況でテストしなければならないことを覚えておいてください-正しい種類のボタンに正確に渡した場合にのみ機能する場合は、任意の種類のボタンを使用するふりをして誰にも恩恵を与えません。

オブジェクト指向プログラミングの問題は、カートを馬の前に置くことです。型を作成する価値があるかどうかを知るために、シグネチャが何であるかを知るために、最初に関数を書く必要があります。ただし、Javaでは、関数シグネチャを作成できるように、最初に型を作成する必要があります。

このため、最初に関数を作成することがどれほど素晴らしいかを確認するために、Clojureコードを作成することをお勧めします。できるだけ漸近的な複雑さを考えて、すばやくコーディングできます。Clojureの不変性の仮定は、Javaの型と同じくらいバグを防ぎます。

私はまだ大規模プロジェクトの静的型のファンであり、現在は関数を最初記述して後でJavaで型に名前を付けることができるいくつかの機能ユーティリティを使用しています。ちょっとした考え。

PS私はmuiポインターを最終的にしました-可変である必要はありません。


6
3つのポイントのうち、1と3は、汎用JButtonと適切な入力パラメーター命名スキームで同様に処理できます。ポイント2は、コードをより緊密に結合するという点で実際には欠点です。ボタンの順序を逆にする場合はどうでしょうか。OK /キャンセルボタンの代わりに、はい/いいえボタンを使用する場合はどうなりますか?YesButtonとNobuttonをとるまったく新しいshowPopupメソッドを作成しますか?デフォルトのJButtonを使用するだけの場合、タイプはすでに存在するため、最初にタイプを作成する必要はありません。
-Nzall

@NateKerkhofsが言ったことを拡張して、最新のIDEは実際の式を入力している間、正式な引数名を表示します。
ソロモンスロー

8

Swingコンポーネントを常に使用して拡張する必要があると教師が実際に信じていないことを喜んで申し上げます。クラスの拡張の練習を強制するための例としてこれを使用しているに違いない。まだ現実のベストプラクティスについてはあまり心配しません。

そうは言っても、現実の世界では、継承よりも構成を優先ます。

「IS-A関係が存在する場合にのみクラスを拡張する必要がある」というルールは不完全です。「...そして、クラスのデフォルトの動作を変更する必要がある」という大きな太字で終わる必要があります

あなたの例はその基準に適合しません。あなたがする必要はありませんちょうどそのテキストを設定するクラス。あなたがする必要はありませんそれにコンポーネントを追加するクラス。これらのことはデフォルトの実装を使用して問題なく実行できるため、継承を追加すると不要な複雑さが追加されます。extendJButtonextendJFrame

extends別のクラスのクラスを見たら、そのクラスが何を変えているのだろうと思います。何も変更しない場合は、クラス全体を調べないでください。

質問に戻ります。Javaクラスをいつ拡張する必要がありますか?本当に、本当に、本当にクラスを拡張する正当な理由があるとき。

具体的な例を次に示します。カスタムペイントを行う1つの方法(ゲームやアニメーション、またはカスタムコンポーネントのみ)は、JPanelクラスを拡張することです。(その上の詳細情報ここでは。)あなたが拡張JPanelあなたがする必要があるため、クラスをオーバーライドするpaintComponent()機能を。これを行うことで、実際にクラスの動作を変更しています。デフォルトのJPanel実装ではカスタムペイントを実行できません。

しかし、私が言ったように、あなたの教師はおそらく、これらの例を単にクラスの拡張の練習を強制する口実として使用しているだけでしょう。


1
ああ待って、Border追加の装飾を描くa を設定できます。またはを作成し、JLabelそれにカスタムIcon実装を渡します。JPanelペイントのためにサブクラスを作成する必要はありません(そしてなぜのJPanel代わりにJComponent)。
ホルガー

1
ここであなたは正しい考えを持っていると思いますが、おそらくより良い例を選ぶことができます。

1
しかし、私はあなたの答えが正しいことを繰り返し述べたいと思います。特定の例とチュートリアルがそれを弱くサポートしていると思います。

1
@KevinWorkman Swingゲーム開発については知りませんが、SwingでいくつかのカスタムUIを作成し、「Swingクラスを拡張しないので、configを介してコンポーネントとレンダラーを簡単に接続できます」がかなり標準的です。

1
「彼らはあなたを強制するための例としてこれを使用しているに違いない...」私の主要なピーチの1つ!教えるインストラクターどのようにひどく錯乱例のを使用してXを行うには、なぜ X.行うために
ソロモン遅い
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.