【问题标题】:java inter object communicationjava对象间通信
【发布时间】:2014-08-02 20:27:59
【问题描述】:

还在学习 Java。

Swing 再次让我问这个问题,但这确实是一个一般的 OO 问题。如果我有一个主类(包含 main()),它会创建一个新对象“A”来做某事,主类现在有对该对象的引用,对象“B”如何访问该对象的属性?

我能想到的唯一方法是让主类创建一个新对象“B”,将对象“A”作为参数传递给构造函数,我想这是可以的。但这不会使事件处理变得困难。

例如,也许这是导致问题的糟糕设计。我有一个带有程序逻辑的大师班,它创建一个标准的 Swing 框架,带有一个菜单,菜单项具有动作侦听器。但是 actionlistener 需要与外部对象交互。

所以一些代码(忽略细节):

主类,包含程序逻辑和保存加载方法等:

public final class TheProgramme implements WindowListener }
    private static final TheProgramme TP = new TheProgramme();
    // Declare Class variables, instance variables etc.

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShewGUI();
            }
        });
    }

    private static void createAndShewGUI() {
        TP.populateAndShew();
    }

    private void populateAndShew() {
        final StandardFrame sF = new StandardFrame("TheProgramme");
        theFrame = sF.getMainFrame();
        theFrame.addWindowListener(this);
        theFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        theFrame.pack(); theFrame.setVisible(true);
    }
...
}

所以我们创建了一个标准框架对象,它创建了一个菜单、空面板和状态栏,但在菜单项上有事件监听器:

public class StandardFrame {
    // Declare instance variables that must be visible to the ActionListener inner class

    public StandardFrame(String theTitle) {
        mainFrame = new JFrame(theTitle);
        mainFrame.setJMenuBar(createMenuBar()); // ... the menu bar and ...
        mainFrame.setContentPane(createBlankPanel()); // ... a blank panel
        java.net.URL imageURL = TheProgramme.class.getResource("images/icon.png");
        if (imageURL != null) {
            ImageIcon icon = new ImageIcon(imageURL);
            mainFrame.setIconImage(icon.getImage());
        }
    }

    public JMenuBar createMenuBar() {
        ActionListener menuEvents = new MenuListener();
        JMenuBar aMenuBar = new JMenuBar();
        JMenu fileMenu = new JMenu("File"); fileMenu.setMnemonic(KeyEvent.VK_F);
        ...
        aMenuBar.add(fileMenu);
        ...
        JMenuItem newItem = new JMenuItem("New", KeyEvent.VK_N); newItem.addActionListener(menuEvents);
        newItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
        ...
        fileMenu.add(newItem);
        ...
        return aMenuBar;
    }
}

class MenuListener implements ActionListener {

    public void actionPerformed(ActionEvent ae) {
        String actionCommand = ae.getActionCommand();
        switch (actionCommand) {
            case "New":
                // !!! here we need to call a method in an object to create a new document object !!!
                break;
             case "Reformat":
                // !!! here we need to call a method in the object created above
         }
     }
}

第一个问题是菜单项上的 actionlistener 调用一个方法来创建一个对象,但它不是对静态方法的调用。 第二个问题是它需要能够在稍后作为另一个菜单选择的结果调用该新对象中的方法。

【问题讨论】:

  • 一些示例代码会有所帮助。
  • 需要更多关于问题的详细信息才能给出答案。目前有点难以理解问题所在。通常,对象从不知道任何其他对象开始;您可以在构造函数中传递详细信息,或者稍后使用 setXxx() 方法设置它们。尝试绘制不同对象的图表并使用它来决定哪个对象需要知道哪个其他对象。通常你应该把 UI 和数据类分开,所以如果你有一个模型对象指向一个 UI 对象,那么你就搞错了。
  • 您似乎想要的是某种MVC 结构。您对当前的结构感到担忧是对的,但可能是出于错误的原因。在某些时候,您需要将某事的引用传递给某个人,以便在需要更改时得到通知,请参阅observer pattern 了解更多详细信息。您还应该研究“编程到接口而不是实现”的概念,这也是相关的
  • 尝试提出MCVE。创建文档的方法在哪里定义,在哪个类中?此外,哪个类维护对需要重新格式化的(选定)文档的引用?这都是StandardFrame 的一部分吗?如果MenuListener 是一个内部类,它可以访问StandardFrame 中的所有内容。
  • @Aqua 我试图保留 StandardFrame 类只是为了制作空白程序屏幕并监听菜单事件,新的文档对象是一个不同的类。问题之一是如何将新对象引用返回到 StandardFrame 以便可以从那里调用方法,此时从动作侦听器调用主程序并创建新文档,但它是一个调用到静态方法。

标签: java swing oop design-patterns


【解决方案1】:

Model-View-Controller 中执行此操作的经典方法是在运行时将对象绑定在一起。控制器,即您的操作侦听器,采用参数来指示要对哪个视图和模型进行操作。

正如 Aqua 所提到的,这种对参数的使用也称为“依赖注入”。

   public static void main( String[] args )
   {
      Model model = new Model();
      View view = new View();
      ActionListener listener = new MyActionListener( model, view );
      view.addActionListener( listener );
   }

   private static class MyActionListener implements ActionListener
   {
      private Model model;
      private View view;

      public MyActionListener( Model model, View view )
      {
         this.model = model;
         this.view = view;
      }
   }

在 Java 中你可以作弊,因为 ActionEvent 有一个指向事件源的指针(通常是生成事件的视图/JComponent。

private static class MyActionListener implements ActionListener
{
  private Model model;

  public MyActionListener( Model model )
  {
     this.model = model;
  }

  @Override
  public void actionPerformed( ActionEvent e )
  {
     JComponent source = (JComponent) e.getSource();
     // source == "view"...
  }
}

要设置新文档,您可以创建“文档持有者”类。 “新”菜单项将新文档放入持有者类。所有其他菜单项从持有者类“获取”文档。这是一个相当严格的 OO 范例,它不使用静态方法或字段,虽然有点乏味。

设置:

   public static void main( String[] args )
   {
      ModelDocumentHolder model = new ModelDocumentHolder();
      View view = new View();
      ActionListener listener = new NewDocument( model );
      view.addActionListener( listener );
      View view2 = new View();
      view2.addActionListener( new RegularListener( model ) );
   }

新的文档监听器:

   private static class NewDocument implements ActionListener
   {
      private ModelDocumentHolder model;

      public NewDocument( ModelDocumentHolder model )
      {
         this.model = model;
      }

      @Override
      public void actionPerformed( ActionEvent e )
      {
         model.setDoc( new Document() );
      }
   }

大多数其他菜单项:

   private static class RegularListener implements ActionListener
   {
      private ModelDocumentHolder model;

      public RegularListener( ModelDocumentHolder model )
      {
         this.model = model;
      }

      @Override
      public void actionPerformed( ActionEvent e )
      {
         JComponent source = (JComponent) e.getSource();
         Document doc = model.getDoc();
         // do stuff...
      }
   }

持有者类:

   private static class ModelDocumentHolder
   {
      private Document doc;

      public Document getDoc()
      {
         return doc;
      }

      public void setDoc( Document doc )
      {
         this.doc = doc;
      }

   }

【讨论】:

    【解决方案2】:

    一般来说,(很难知道这是否是您问题的答案,因为它很模糊)这就是 setModel(...)addListener(...) 的用途。

    “constructor coordinator”又名“master class”,创建模型(swing Model 类)。它创建视图(JWidget)并设置视图的模型。在 Swing 中,很容易依赖默认构造模型(使用 JWidget 默认构造函数填充),但也许在您的情况下,您应该找到导致问题的一个小部件并重写它以使模型设置显式。

    如果您扩展了 Jwhatever,请记住 setModel(...) 通常会执行类似的操作

    if (this.model != null) {
       this.model.removeListener(this);
    }
    // clear the cached last "view" of the model
    clearCachedData(...);
    if (model != null) {
       this.model = model;
       // restore the "view" of the new model.
       grabCachedData(...);
       this.model.addListener(this);
    }
    

    【讨论】:

      【解决方案3】:

      我希望我对问题的解释是正确的。您可以向操作实现注入/提供您需要的任何对象。这是一个示例,它使用一个接口进行更好的抽象作为来自actionPerformed 的回调。当动作完成时,它会调用它的回调来通知任何感兴趣的人。在这种情况下,面板会收到通知并使用一些文本更新其文本区域。

      界面:

      public interface ActionCallback {
          public void documentCreated(String name);
      }
      

      这是用户界面:

      import java.awt.BorderLayout;
      import java.awt.Component;
      import java.awt.Dimension;
      import java.awt.event.ActionEvent;
      
      import javax.swing.AbstractAction;
      import javax.swing.JFrame;
      import javax.swing.JMenu;
      import javax.swing.JMenuBar;
      import javax.swing.JMenuItem;
      import javax.swing.JOptionPane;
      import javax.swing.JPanel;
      import javax.swing.JScrollPane;
      import javax.swing.JTextArea;
      import javax.swing.SwingUtilities;
      
      public class TestAction extends JPanel implements ActionCallback {
          private JTextArea area;
          public TestAction() {
              setLayout(new BorderLayout());
              area = new JTextArea();
              add(new JScrollPane(area));
          }
      
          public Dimension getPreferredSize() {
              return new Dimension(200, 200);
          }
      
          @Override
          public void documentCreated(String name) {
              area.append(String.format("Created %s\n", name));
          }
      
          public static class NewAction extends AbstractAction {
              private ActionCallback callback;
              private Component parent;
      
              public NewAction(ActionCallback callback, Component parent){
                  super("New");
                  this.callback = callback;
                  this.parent = parent;
              }
      
              @Override
              public void actionPerformed(ActionEvent e) {
                  String value = JOptionPane.showInputDialog(parent, "Name", "new name");
                  if (value != null){
                      callback.documentCreated(value);
                  }
              }
          }
      
          public static void main(String[] args) {
              SwingUtilities.invokeLater(new Runnable() {   
                  public void run() {   
                      JFrame frame = new JFrame("Test");
                      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                      frame.setLocationByPlatform(true);
      
                      TestAction panel = new TestAction();
                      frame.add(panel);
      
                      JMenuBar menuBar = new JMenuBar();
                      JMenu menu = new JMenu("Menu");
                      menuBar.add(menu);
      
                      JMenuItem item = new JMenuItem(new NewAction(panel, frame));
                      menu.add(item);
                      frame.setJMenuBar(menuBar);
      
                      frame.pack();
                      frame.setVisible(true);
                  }
              });
          }
      }
      

      【讨论】:

        【解决方案4】:

        将您自己的引用 (A) 传递给其他对象 (B) 在 GUI 代码中经常发生。您可以使用上下文对象,将其传递给所有类,其中包含对相关引用的引用,并且可能包含一些全局信息。在简单的情况下,“主程序类”被用作上下文并被传递。

        根据您使用的类,这也可能有用:Component#getParent()

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-01-29
          • 1970-01-01
          • 2019-03-13
          • 1970-01-01
          • 1970-01-01
          • 2013-03-04
          • 2013-04-06
          相关资源
          最近更新 更多