複数の actionListener のリファクタリング
-
16-09-2019 - |
質問
私は現在、多くの ActionListener が定義されている Java コードに取り組んでいます (それぞれに 1 つ) JButton
) ボタンは約 60 個あります。これらはすべて、 JButton.addActionListener
方法。コードが非常に乱雑に見えるため、コードをよりきれいに見せるためにこれをリファクタリングする方法を考えてきました。私は、リスナーを、基本的にそれぞれがリスナーを返す静的メソッドの負荷を持つ別のクラスに取り込むことを考えました。これは、コードが次のようになることを意味します addActionListener(GetActionListener.addActionListener())
. 。これによりすっきりしますが、実際にはエレガントな解決策ではないと感じます。また、リスナー名とリスナー自体の KV ペアを保持する静的な最終マップも考えました。ただし、これもまだあまり洗練された解決策とは言えません。誰かアイデアがある人はいるだろうかと思ったのですが?すべての actionListener はかなり異なっているとも言っておきます。
解決
私が直接ActionListener
を使用してアクションを追加しないことをお勧め。あなたがこの方法を行う場合には、非再利用可能になります。代わりにjavax.swing.Action
クラスでのあなたの行動を包みます。あなたが好きな場所、アクションを再利用できるようにします。例えばのために、今、あなたはコピーアクションのメニューのショートカットやツールバーのコピーボタン言うために同じアクションを使用することができます。
基本的な考え方は、GUI要素と直結実行可能な行動をしないことです。
は、今、あなたの質問に来ます。私は、パブリックメソッドpublic repository of actions
と言う、というクラス、ActionRepsoitoryでAction getAction(String)
になるだろう。あなたの行動のそれぞれは、あなたがリポジトリからアクションを取得するために使用定数文字列で識別されるだろう。通常、その文字列は、要素のactionCommand
だろう。あなたはActionRepositoryでアクションを管理する方法、HasMapまたは何を経由して、あなたに完全に依存します。
これはどのようにほとんどの留年コードでそのDOEN、私の知る限ります。
他のヒント
なかったこの質問するのない重複(すごい...それは答えた質問の複製)が、答えは適用されなければならない。
あなたの内部クラスはちょうどあなたが(「右」の私の定義に)「間違っている」それをやっている、その後、外側のクラスの内部メソッドを呼び出すよりも多くを行っている場合。投稿のコードでは呼び出しはインクリメントする()とデクリメント()はそれを行うには「正しい」方法です。リスナーは、外側のクラスにメソッド呼び出しを転送する必要があり、コードをリファクタリングすると、コードを管理しやすくするために開始するには良い場所です。
言われていること... UI上の60個のボタン!本当に!痛いです!それらはすべて一つの画面上にあるか、それはタブか何かで行われていますか? (それは、タブか何かであれば、私は答えに提供するより多くを持っている)。
あなたは、あなたが通常の方法として、すべての60回のアクションを実装することができ、指定されたメソッド名を呼び出すためにリフレクションを使用していますActionListener
の特殊なサブクラスを作ることができます。
package com.example;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MethodAsActionListener implements ActionListener {
private Object receiver;
private Method method;
public MethodAsActionListener(Object receiver, String name) {
this.receiver = receiver;
try {
this.method = receiver.getClass().getDeclaredMethod(name);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@Override
public void actionPerformed(ActionEvent event) {
try {
method.invoke(receiver);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
と、あなたの場合、あなたのクラスにメソッドcall
private call(String name) {
return new MethodAsActionListener(this, name);
}
次のようにあなたはあなたの行動を追加することができます。
button1.addActionListener(call("methodName1"));
button2.addActionListener(call("methodName2"));
button3.addActionListener(call("methodName3"));
button4.addActionListener(call("methodName4"));
button5.addActionListener(call("methodName5"));
これらの方法のいずれかが欠落している場合はUIが構築されたときに(私たちはアクションリスナーが作成される方法をルックアップするので)、プログラムが失敗します。どのアクションがトリガされたときに、まだ完全に遅延バインディングよりも優れてコンパイル時のように素敵ではありませんが、ます。
あなたが提案したようなことをお勧めします--すべてのイベントをサブスクライブする単一のリスナークラスを作成します。ただし、おそらくイベントごとにクラスの異なるインスタンスを使用し、この特定のイベントを一般的にどのように処理するかをインスタンス (コンストラクター内) に指示したいと考えます。
この利点は、通常は非常に似ているため、リスナー内のコードをより少ないメソッドに分解できることです。場合によっては、単一のメソッドにまとめることもできます。
メニュー作成の「純粋なディスパッチ」状況で私が使用したトリックの 1 つは、メニュー、メニューの構造、および各メニュー項目がデータ内でリンクするメソッドを指定することでした。少し反省が必要ですが、うまくいきます。
実際のところ、見てみましょう。
はい、クラスを Google ドキュメントに保存しました:) データは次のように指定されました。
final static String[] menus = { "File:*", "Save:save", "Load:load", "(", "Print:-", "Preview:preview", ")", "Quit:quit" };
これを解析しただけです。開始によりファイルはトップレベル項目になり、保存は「Save」メソッドを呼び出し、ロードは「Load」メソッドを呼び出します。印刷はサブメニュー(したがって括弧)であり、その下にプレビューがあり、印刷はバインドされません何に対しても。
この文字列を使用すると、1 回の呼び出しでメニュー全体を作成してバインドできます。
こちらです 私のソースコード それで遊びたいなら。
先頭の「TestMenu」クラスは、buildMenus メソッドの使用方法を示すテスト クラスです。
これは何年も前に行われたものなので、今は違うやり方をしているかもしれませんが、うまくいきました。実際にメニューを生成するのが気に入るかどうかはわかりません。文字列パーサーで各項目の文字列に分割するのではなく、単一の文字列を使用するようにすると思います。これにより、各項目が空白で区切られるようにするのが簡単になるはずです。 ..
より良い API は、次のようなバインド メソッドかもしれません。
bind(this, new JButton("Save"), "save", this);
ここで、保存ボタンを押すと、これ (または渡した他のオブジェクト) に対して save メソッドが呼び出されます。「save」パラメータをオプションにして、パラメータが存在しない場合に呼び出すメソッドとして JButton.getText().toLower() を使用することもできます(これは設定前の慣習だと思います)
メニューの作成とメニューの関係をデータに抽象化したかったため、メニューではこの方法を実行しませんでした。
この方法でコーディングすることは、Java で MVC を分離するための素晴らしい方法であることに注意してください。すべてのコントローラー コードをビューから削除できます。
package hEvil;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.border.EmptyBorder;
public class JDial extends JDialog {
private static final long serialVersionUID = -26565050431585019L;
private final JPanel contentPanel = new JPanel();
public static void main(String[] args) {
try {
JDial dialog = new JDial();
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setVisible(true);
dialog.setTitle("Heavy Evil");
dialog.setBackground(Color.WHITE);
} catch (final Exception e) {
e.printStackTrace();
}
}
public JDial() {
setBounds(0, 0, 1300, 800);
getContentPane().setLayout(new BorderLayout());
contentPanel.setLayout(new FlowLayout());
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
getContentPane().add(contentPanel, BorderLayout.CENTER);
JPanel windowPane = new JPanel();
windowPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
getContentPane().add(windowPane, BorderLayout.SOUTH);
{
JButton cancelButton = new JButton("Exit");
cancelButton.setActionCommand("Exit");
windowPane.add(cancelButton);
cancelButton.setBounds(0, 0, 1200, 700);
}
{
JPanel textPane = new JPanel();
textPane.setLayout(new FlowLayout(FlowLayout.LEFT));
getContentPane().add(textPane, BorderLayout.NORTH);
textPane.setVisible(true);
{
JTextArea textArea = new JTextArea("Username", 2, 15);
textPane.add(textArea);
textArea.setWrapStyleWord(true);
textArea.setEditable(true);
textArea.setFont(Font.getFont(Font.SANS_SERIF));
textArea.setVisible(true);
textArea.enableInputMethods(isEnabled());
textArea.computeVisibleRect(getBounds());
textArea.setBackground(Color.GRAY);
JTextArea textArea2 = new JTextArea("Password", 2, 15);
textPane.add(textArea2);
textArea2.setWrapStyleWord(true);
textArea2.setEditable(true);
textArea2.setFont(Font.getFont(Font.SANS_SERIF));
textArea2.setVisible(true);
textArea2.enableInputMethods(isEnabled());
textArea2.computeVisibleRect(getBounds());
textArea2.setBackground(Color.GRAY);
}
{
JButton registerButton = new JButton("Register");
textPane.add(registerButton);
}
{
JButton newButton = new JButton("Submit");
textPane.add(newButton);
newButton.setEnabled(true);
getRootPane().setDefaultButton(newButton);
newButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
JFrame newFrame = new JFrame("Welcome");
newFrame.setVisible(true);
newFrame.setBackground(Color.BLACK);
newFrame.setBounds(0, 0, 580, 200);
JPanel newPanel = new JPanel();
newFrame.add(newPanel);
dispose();
JButton nuButton = new JButton("Mario");
newPanel.add(nuButton);
JButton nuButton2 = new JButton("Kirby");
newPanel.add(nuButton2);
JButton nuButton3 = new JButton("Mew Two");
newPanel.add(nuButton3);
JButton nuButton4 = new JButton("Vegeta");
newPanel.add(nuButton4);
JButton nuButton5 = new JButton("Tidus");
newPanel.add(nuButton5);
JButton nuButton6 = new JButton("Link");
newPanel.add(nuButton6);
JButton nuButton7 = new JButton("Master Chief");
newPanel.add(nuButton7);
JButton nuButton8 = new JButton("Snake");
newPanel.add(nuButton8);
JButton nuButton9 = new JButton("Cash");
newPanel.add(nuButton9);
JButton nuButton10 = new JButton("Lara");
newPanel.add(nuButton10);
JButton nuButton11 = new JButton("Max");
newPanel.add(nuButton11);
JButton nuButton12 = new JButton("Spyro");
newPanel.add(nuButton12);
JButton nuButton13 = new JButton("Sephiroth");
newPanel.add(nuButton13);
JButton nuButton14 = new JButton("Scorpion");
newPanel.add(nuButton14);
}
});
}
}
}
}
//AND I WANT TO BE ABLE TO IMPLEMENT EACH BUTTON FROM ANOTHER CLASS
//FROM ACTIONEVENT WITH SOMETHINGS SIMILAR TO nuButtonX.actionImplemented...
//CALLING THE SAME ACTIONLISTENER IF I CAN AND THEN CREATING A CLASS FOR
//MODIFICATIONS AT EACH INSTANCE.
enter code here
package hEvil;
import java.awt.event.ActionEvent;
import javax.swing.JFrame;
public class ActoEve extends ActionEvent {
/**
*
*/
private static final long serialVersionUID = -2354901917888497068L;
public ActoEve(Object arg0, int arg1, String arg2) {
super(ActionEvent.ACTION_PERFORMED, arg1, "flame_001.jpg");
// TODO Auto-generated constructor stub
}
public void actionImplemented(ActionEvent evt1) {
JFrame nuFrame = new JFrame();
nuFrame.setVisible(true);
nuFrame.setBounds(0, 0, 300, 200);
// TODO Auto-generated constructor stub
}
}