简体   繁体   中英

StackOverflow confusion

I'm a java newbie, and am having a very confusing issue with StackOverflow errors / ability to access files between classes. I understand that the underlying cause is likely that I have some recursive call, but the syntax of fixing it is escaping me. I think it has something to do with how the classes are linked through one extending another -- but, if the InputScreen class doesn't extend the ViewController, I can't access the methods there that I need. I've put the high-level code below (making a program to track gas mileage).

Goal of this is to be able to open an xml file with some historical mileage data (using the doOpenAsXML() method), then allow the user to add data to some text fields (defined in the InputScreen class), add another data point to the ArrayList, and then save using the doSaveAsXML method.

Anyone have ideas on how to make this work? Thanks!!!


// Simple main just opens a ViewController window
public class MpgTracking {
    public static void main(String[] args) {
        ViewController cl = new ViewController();
        cl.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        cl.setVisible(true);
    } // end main
}

public class ViewController extends JFrame {

    // the array list that I want to fill using the historical data
    public ArrayList<MpgRecord> hist; 

    public ViewController() {
         doOpenAsXML();  // open historical data, put into 'hist'
         InputScreen home = new InputScreen ();
    }

    public void doSaveAsXML() {
    // ...long block to save in correct xml format
    }

    public void doOpenAsXML() {
    // ...long block to open in correct xml format
    }

}

public class InputScreen extends ViewController { // statements to define a screen with text fields and a 'Save' button // statements to create a listener on the Save button // statements to add to the ArrayList hist, opened in the ViewController method doSaveAsXML(); }

Are you saying that InputScreen extends ViewController? That seems like it would be an infinite recursive loop right there in the constructor. Also,

public ViewController() {
     doOpenAsXML();  // open historical data, put into 'hist'
     InputScreen home = new InputScreen ();
}

doesn't do anything. You create a new InputScreen set it to the variable home, which promptly gets GCed as soon as the constructor finishes.

This seems to be the root cause of your problem:

but, if the InputScreen class doesn't extend the ViewController, I can't access the methods there that I need.

To access (non-static) methods in another class, you need an object of this class. In your case, the InputStream would need to have a ViewController object, not to be a ViewController object. (On the same lines, the ViewController should not be a JFrame, but have one - although this does not give problems here.)

If you change this, you don't get your constructor-loop.

public class ViewController {

    ...

    public ViewController() {
         doOpenAsXML();  // open historical data, put into 'hist'
         InputScreen home = new InputScreen (this); // give myself to our new InputScreen.
         // do something with home
    }

    public void doSaveAsXML() {
    // ...long block to save in correct xml format
    }

    public void doOpenAsXML() {
    // ...long block to open in correct xml format
    }


}

public class InputScreen {

    private ViewController controller;

    public InputScreen(ViewController contr) {
       this.controller = contr;
    }


    void someMethod() {
        // statements to define a screen with text fields and a 'Save' button
        // statements to create a listener on the Save button
        // statements to add to the ArrayList hist, opened in the ViewController method
        controller.doSaveAsXML();
    }
}

Short answers: Composition over inheritance . Program against interfaces . Principle of Least Privilege . Role interfaces .

I'd suggest you do a google on them after reading my post ;)

For one thing, you do not need InputStream to inherit from ViewController as VieController.doSaveAsXML() is public.

Even if it had private|protected|package visibility, we should not use inheritance only to access a method on some other class that is not visible to us. Inheritance is for inheriting , refining or extending behavior, not for accessing methods.

You can always pass the controller as an argument type to your input stream. If your input stream needs to access all methods in the controller, pass the controller as is (or have the controller implement an interface with all the methods in it.)

First important thing to notice is that your InputStream doesn't really need access to a controller. It only need access to something that can do file opening functions for it - it needs a file opener .

Second important thing to notice is that the controller (typically) doesn't need to know how to open files either. A controller is typically an organizer or orchestrator (for lack of a better word.) As such, it doesn't need to be a file opener (or implement file opening logic itself.) The controller just needs a reference to aa file opener . Just like the input stream , the controller needs to simply delegate file opening logic to a file opener .

In other words:

  1. your input stream needs to have access to a file opener
  2. your controller also only needs to have access to a file opener
  3. your controller initializes your input stream, and so, it needs to pass a file opener to it during initialization
  4. because of the above, the controller itself needs to receive, instantiates or be configured with a file opener itself.
  5. since the file opener is external to both the stream and the controller, the file opener should be an interface (which can be implemented by a xml file opener or any opener for that matter.)

java pseudocode:

public interface FileOpener
{
  public void doOpen();
}

public class XMLOpener implements FileOpener
{
  public void doOpen(){ ... do xml specific file opening ... }
}

public interface Stream
{
   /** inits stream with controller     
    **/
   public void init(FileOpener FileOpener);
   public void kickIt();
}

public interface Controller 
{
   // defines other methods implemented by actual interfaces.
   public void kickIt();
}

public class ViewController implements Controller 
{
   private Stream stream;
   private FileOpener opener;

   public ViewController( final Stream stream. final FileOpener opener )
   {
     this.stream = stream;
     this.opener = opener;

     this.stream.init(this.opener);
   }


   public void kickIt(){ stream.kickIt() );
}

public class InputStream implements Stream
{
   private FileOpener opener;

   public InputStream()
   {
   }

   public Stream init(final FileOpener opener )
   {
     this.opener = opener;
   }

   public void kickIt()
   {
     this.opener.doOpen();
   }
}

....

Controller controller = new ViewController( 
      new InputStream(), 
      new XMLOpener() );

Whether you go all the way making everything working against interface handlers, that's a design decision that we have to make with every project. But this approach (or something inspired by it) is typically the way to go. It is much harder this way (but not impossible) to come up with cyclical dependencies, and when they do occur, they are a matter of configuration, and not of an intrinsic problem with you object model.

Hope it helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM