1

I'm just starting learning Java after a few years of HTML/CSS coding so hopefully I'm not asking a old or stupid question here but any help explaining this problem would be very much appreciated.

I'm currently working through the Stanford CS106A online material and I've reached week 6, Assignment 2, Question 3 (http://see.stanford.edu/materials/icspmcs106a/13-assignment-2-simple-java.pdf).

As you can see it requires the placement of various objects on the screen to create the Graphics Hierarchy, as described. My plan was to use the centre coordinates to relatively place all the objects on the screen. However I've hit a problem that I can't seem to find an answer to. The course describes how Method Decomposition should allow each method to handle one problem (Single Responsibility Principle, I believe) so I have written the first part of my code as such:

//Import any libraries
import acm.program.*;
import acm.graphics.*;

    public class GraphicsHierarchy extends GraphicsProgram {

//Define constants
static final int BOX_WIDTH = 200;
static final int BOX_HEIGHT = 75;



public void run() {
    placeGRect();
}   

//Find centre x & y
double centre_x = getWidth() / 2; //check this
double centre_y = getHeight() * 0.5;//and this

//placeGRect method
public void placeGRect() {
    for (int count = 0; count < 4; count++) {
        GRect box = new GRect (BOX_WIDTH, BOX_HEIGHT);
        add(box);
        switch (count) {
        case 0:
            box.setLocation(centre_x, 75);
            break;
        case 1:
            box.setLocation((centre_x * 0.5), 250);
            break;
        case 2:
            box.setLocation(centre_x, 250);
            break;
        case 3:
            box.setLocation((centre_x * 1.5), 250);
            break;
        }
    }
}
}

However this doesn't work due to the centre_x & centre_y producing zero values. I discovered this by changing the program to a ConsoleProgram and having the getWidth & getHeight lines inside the run() method (and println their values on screen), which then produced the required values but didn't pass them to the GRect method (so still didn't work). However if I have the getWidth/getHeight lines listed out of the run() then they don't produce any values for relative positioning.

My question is given that each method should handle one task and (as much as possible) methods should be defined out of the run() method, then how I can I get the getWidth/getHeight values to the placeGRect() method without having one big block of code within the run() method. Which I understand is bad practise.

I'm not after any code to solve this, I really need to understand the principles of this so I can write effective code in the future. I prefer understanding to parrot-fashion code copying.

Thanks in advance for any help.

sh3llcmdr
  • 21
  • 1
  • 6
  • 1
    Your course name and assignment number is useless to most; why not give it some useful relevant title? – Bala R May 05 '11 at 14:21
  • If `getWidth()` and `getHeight` are on the same object as `getGRect()` then why do you *need* to pass them in? – Jeremy May 05 '11 at 14:28
  • 1
    Apologies, I pressed submit question before I changed the title to something more useful. – sh3llcmdr May 05 '11 at 14:52

4 Answers4

1

In your specific example:

You have declared centre_x and centre_y as instance variables. When your program first creates an instance of GraphicsHierarchy the order of object creation is as such:

  1. ClassLoader loads the class... static variables (BOX_WIDTH,BOX_HEIGHT) are assigned specified values;

  2. Space is allocated on the heap for an instance of GraphicsHierarchy (enough space to hold the instance variables - a double for centre_x and a double for centre_y- including space for base class instance variables)

  3. Instance variables are set to default values: centre_x = 0, centre_y = 0

  4. The GraphicsHierarchy default constructor is called (which does nothing other than call the base class constructor - GraphicsProgram).

  5. The base class will go through steps 1-4 and when it's finished execution returns to GraphicsHiearchy which now evaluates explicit instance variable initializers before executing any remaining constructor statements (which in the case of the default constructor, there are none).

(additional reference on this process http://java.dzone.com/articles/java-object-initialization)

Having said all of that, it would appear that when your class GraphicsHierarchy gets to step 5 and tries to assign values to centre_x and centre_y, the subsystem that getWidth and getHeight rely upon is not ready (i.e. a window or canvas has not been created yet, so the methods return 0). But when you moved your assignments inside run and getWidth/getHeight returned values, that would imply whatever method is calling run has first gone through the necessary window creation steps.

Etienne de Martel's suggestion is fine. It delays assignment of your centre values until right before they are needed. If you'd rather, you could create an init method and move the assignments inside the init method, and then call init as the first step of run

private void init() {
    centre_x = getWidth / 2;
    centre_y = getHeight * 0.5;
}

public void run() {
    init();
    placeGRect();
}

This is nearly the same thing as Martel's suggestion, although if you find later you have other initialization code that needs to happen, you can throw it in the same place.

As for crafting flexible code you might think about renaming placeGRect to placeGRects and passing in array of points (or Collection if you prefer) placeGRects(Point[] points)

(you can use Java.awt.Point or define your own Point class)

In this way, your placeGRects method is simplified. It no longer decides how many boxes to render (the array that is passed in does). It also doesn't determine where those new boxes are located (again the array of Point objects do). It simply loops through the size of the array, makes a new box, adds it, and sets the location.

private Point[] boxPoints;

public void run() {
    init();
    placeGRects(boxPoints);
}

public void placeGRects(Point[] points) {
    for(int i=0;i<points.length;i++) {
        GRect b = new GRect(BOX_WIDTH,BOX_HEIGHT); 
        add(b);
        b.setLocation(points[i].x,points[i].y);
    }
}

And you can put your Point array initialization inside your new init() method.

private void init() {
    centre_x = getWidth / 2;
    centre_y = getHeight * 0.5;
    boxPoints = {new Point(centre_x, 75),new Point(centre_x * 0.5, 250)}; 
}

It makes your code easier to understand and modify when needed.

new Thrall
  • 970
  • 1
  • 12
  • 20
  • Although in this particular example, you don't even need to pass boxPoints in as a param, since it's a member variable. :) placeGRects should probably also check that points is not null, before it tries to access its length property. – new Thrall May 05 '11 at 17:51
  • 1
    thank you so much for that. It was just the kind of answer I was looking for and now gives me the better understanding I needed. I now have a clearer picture of why it was failing. Thanks – sh3llcmdr May 05 '11 at 20:37
  • well I used your suggestions of using an 'init()' method but it would appear that has its problems as well. Firstly Eclipse complains that the 'init()' must be public, which is not ideal. So if I make 'init()' public then place the method call in 'run()' it still doesn't resolve the 'centre_x'/'centre_y' for the 'placeGRect' method. I have now found a happy medium which is to call the centre points at the start of the 'placeGRects()' method they are then available for use, seems to work(so far). I don't know why I didn't think of that first. Thanks again for all your help. – sh3llcmdr May 06 '11 at 13:50
  • @Steve Odd. The compiler error and the fact that your centre values were not being set makes me think the GraphicsProgram base class already has an Init method that you were inadvertently overriding. That would explain why the compiler was forcing it to be public and could explain the 0 centre values. It's possible your override prevented the base class from completing setup steps that normally it would do in Init(). If you rename Init() to MyInit() it might solve those issues. Sounds like you found a solutiion anyway. p.s. I added missing return types in some of my examples. GL! – new Thrall May 06 '11 at 16:24
0

Perhaps I don't understand your question, but why not pass them as parameters?

protected void placeGRect(double centre_x, double centre_y) {
    // ...
}

You can then call placeGRect like so:

public void run() {
    placeGRect(getWidth() / 2, getHeight() * 0.5);
}
Etienne de Martel
  • 34,692
  • 8
  • 91
  • 111
  • The reason that I can't pass them as parameters is that each GRect object will have a different position relative to the centre. So I need to define the centre and then position each GRect (using the for loop). – sh3llcmdr May 05 '11 at 14:53
  • @Steve I still don't get what exactly _is_ your problem. – Etienne de Martel May 05 '11 at 15:15
  • I think my lack of knowledge is not helping me be clear here. Right, let me try and explain. If the line: double centre_x = getWidth() / 2; double centre_y = getHeight() * 0.5; are in the run() method then the centre coordinates are available as the "screen" object is live and in use. However those coordinate cannot be passed to the "centre_x" & "centre_y" within the switch cases. How can I determine the centre point and then pass those values to the "centre_x" & "centre_y" once they are called with placeGRect. – sh3llcmdr May 05 '11 at 15:25
  • 1
    @Steve actually they can access those values within the switch. If you change the code as Etienne has suggested, centre_x and centre_y will be calculated the instant before placeGRect begins execution, and your switch will have access to the variables the same as if they had been left as instance variables. – new Thrall May 05 '11 at 17:02
  • apologies I dismissed your answer as I misunderstood but thanks (again) to new Thrall I see what you were suggesting. Another great answer that has helped me understand a little bit more Java. – sh3llcmdr May 05 '11 at 20:39
0

Very nice question! How to compose your methods is a matter of intuition rather than strict guidelines.

Certainly, methods should be focused on doing one thing and one thing only. Firstly, having short methods (even one-liners!) improves the understandability of the code. As a very rough example, think of this:

if (DateUtils.before(ticket.getExpirationDate(), new Date())) {
   accept(ticket);
}

and then this

if (isNotExpired(ticket)) {
   accept(ticket);
}

...

private boolean isNotExpired(Ticket t) {
   return DateUtils.before(t.getExpirationDate(), now());
}

private Date now() {
  return (new Date());
}

Pay attention to how the introduction of one line methods isNotExpired() and now() significantly improved your undestanding of what the code does.

Here's another example, this time that has to do with constructing objects:

Loan l1 = new Loan(15000, 36, f7.2, 2.5);
Loan l2 = new Loan(15000, 36, f7.2);

vs.

Loan l1 = Loan.newSubsidizedLoan(15000, 36, f7.2, 2.5);
Loan l2 = Loan.newNormalLoan(15000, 36, f7.2);

Note in this example how wrapping the constructors in two different methods significantly improves the documentation of code (without even needing to write comments);

If you are interested on the general topic of coding style, you should read this book.

Cheers

L.

Lefteris Laskaridis
  • 2,292
  • 2
  • 24
  • 38
  • Thanks for the book suggestion and answer. However, I'm still stuck how to have tidy method construction/decomposition and being able to do the problem without having a big chunk of code in the run() method. Am I just being really anal and should I just get it working whatever? – sh3llcmdr May 05 '11 at 15:35
  • From what i understood from your question, you are more interested in writing "clean code". Bare in mind that this topic involves more than how to compose your methods and this takes time and experience to develop (you wouldn't learn that just form looking at some answers in stackoverflow anyway). I would really suggest to start reading the book. btw, no i don't think your are anal with this rather on the correct path. – Lefteris Laskaridis May 05 '11 at 16:25
  • @Steve It would be ideal if you could move all your init code outside of run, and into the object constructor, but unfortunately this isn't always possible. In your particular case, some of the needed information isn't available to you until run is called. You can still avoid monolithic code though, by doing your best to separate the logic into distinct units. – new Thrall May 05 '11 at 17:26
  • Thank guys, I'm trying to start out with good habits. Being a guitar player I know it's better to learn it right than to correct bad habits further down the road. – sh3llcmdr May 05 '11 at 20:42
-1

Your code doesn't seem to include the getWidth() and getHeight() methods. Also, the following piece of code is totally wrong as a placement and should be placed in a constructor:

double centre_x = getWidth() / 2; //check this
double centre_y = getHeight() * 0.5;//and this

Should become

private double centre_x;
private double centre_y; 
GraphicsHierarchy(){
    centre_x = GraphicsHierarchy.BOX_WIDTH / 2;
    centre_y = GraphicsHierarchy.BOX_HEIGHT * 0.5;
}

This code will at least compile, but consider the solution described below, which is even better.

Considering that you have defined BOX_WIDTH and BOX_HEIGHT as static variables, you can always find centre_x and centre_y. Therefore, you don't even need to define BOX_WIDTH and BOX_HEIGHT

You can define your class like this:

//Import any libraries
import acm.program.*;
import acm.graphics.*;

public class GraphicsHierarchy extends GraphicsProgram {
public void run() {
    placeGRect();
}   
//Define constants
public static final double CENTRE_X= 100.00; 
public static final double CENTRE_Y = 37.50;
//placeGRect method
public void placeGRect() {
    for (int count = 0; count < 4; count++) {
        GRect box = new GRect (200, 75);
        add(box);
        switch (count) {
        case 0:
            box.setLocation(GraphicsHierarchy.CENTRE_X, 75);
            break;
        case 1:
            box.setLocation((GraphicsHierarchy.CENTRE_X * 0.5), 250);
            break;
        case 2:
            box.setLocation(GraphicsHierarchy.CENTRE_X, 250);
            break;
        case 3:
            box.setLocation((GraphicsHierarchy.CENTRE_X * 1.5), 250);
            break;
        }
    }
}
}

In my opinion, you can go even further by eliminating all computations and replace such stuff

GraphicsHierarchy.CENTRE_X * 1.5 

with

150

Come on, have it easy on your Virtual Machine! Your class uses a whole load of static information, so there is no need for so much computation. But having a BOX_WIDTH and BOX_HEIGHT is completely useless as constants, as they are used only internally and only on one place. Calculating centre_x and centre_y out of the BOX_WIDTH and BOX_HEIGHT is also useless, as as they are final, you can easily do the computation yourself and reduce unnecessary creation of variables.

In addition, you don't use the centre_y value anywhere, so you should ditch it.

To further add some helpful advice, a decent IDE, like NetBeans, Eclipse, or IntellIJIDEA should have code completion and syntax highlighting and will help you immensely in becoming a better (or more knowledgable, which is even better) programmer.

Nikola Yovchev
  • 9,498
  • 4
  • 46
  • 72
  • getWidth(), getHeight() are coming from the base class. They are the width and height of the window/canvas. They are distinct from the size of the Boxes the OP is trying to render. They are not Constants, which invalidates all of your suggestions. – new Thrall May 05 '11 at 16:58
  • @ new Thrall Thanks for that. I thought it was me being dim. – sh3llcmdr May 05 '11 at 20:40