Cartesian Plane Lesson 3

In this lesson we'll address the issue of encapsulation (encapsulate: "to enclose in or as if in a capsule," Merriam-Webster). The subject of encapsulation can be addressed at many different levels. In Java, it can refer to breaking systems into  packages, packages into objects, objects into properties and methods, methods into sub-methods, etc. In this discussion we are mainly looking at properties and methods.

GitHub repository: Cartesian Plane Part 3

Previous lesson: Cartesian Plane Lesson 2: Drawing the Grid Lines

Lesson 3: Encapsulation

1. Pieces and Properties

Figure 1 shows what our final version of the Cartesian Plane graphic might look like, before we plot any equations. Let's break it down into pieces:

grid (this piece includes the property pixels-per-unit)
main window
  +-- margins
  |     +-- top
  |     +-- right
  |     +-- bottom
  |     +-- left
  +-- grid lines
  +-- axes
  +-- tic marks (the lines across the x- and y-axes)
  |     +-- minor (the shorter lines)
  |     +-- major (the longer lines)
  +-- labels (on the tic marks)
  +-- text (in the margins)

        +-- top
        +-- right
        +-- bottom
        +-- left

A couple of notes on the above:

  1. The grid lines, axes and tic marks could be further broken into categories of horizontal and vertical, but let's not distinguish between orientation.
  2. We still have to talk about the bits of the graphic that represent plots, but let's leave that for later.
  3. For now, let's also leave out a discussion of marginal text. 

So, leaving out text and plot, we have six categories of beast that have to be described. Each category is going to have certain properties. A list of the properties of each category follows: 

  1. General grid properties
    • Pixels-per-unit (the grid unit)
  2. Main window properties
    • Width
    • Height
    • Background color
  3. Top margin properties
    Note: A horizontal margin extends the full width of the component that contains it (the main window in this case); its width describes the distance between its minimum and maximum y-coordinates. A vertical margin extends the full height of the component that contains it; its width is the distance between its minimum and maximum x-coordinates.
    • Width
    • Background color
  4. Right margin properties
    • Width
    • Background color
  5. Bottom margin properties
    • Width
    • Background color
  6. Left margin properties
    • Width
    • Background color
  7. Grid line properties
    Note 1: horizontal and vertical lines have the same properties (the same color, weight and spacing).
    Note 2: horizontal lines have a length that spans the width of the rectangle that contains the grid; vertical lines have a length that spans the height of the rectangle that contains the grid.
    • Weight (the thickness of the line)
    • Color
    • Lines per unit
    • Draw grid lines (true or false; false means the grid lines won't be visible)
  8. Axis properties
    Note 1: the horizontal and vertical axes have the same properties (the same color and weight).
    Note 2: the horizontal axis has a length that spans the width of the rectangle that contains the grid; the vertical axis has a length that spans the height of the rectangle that contains the grid.
    • Weight (the thickness of the axis)
    • Color
  9. Minor tic properties
    Note: the horizontal and vertical tics have the same properties (the same color, weight, length and spacing).
    • Weight (the thickness of the tic)
    • Color
    • Length
    • Marks per unit
    • Draw tic marks (true or false; false means the minor tic marks won't be visible)
  10. Major tic properties
    • Weight (the thickness of the tic)
    • Color
    • Length
    • Marks per unit
    • Draw tic marks (true or false; false means the major tic marks won't be visible)
  11. Label properties
    Note 1: for the sake of simplicity, the position of a label will always correspond to the position of a major tic mark; minor tic marks will not be labeled.
    • Font name
    • Font style (bold, italic, plain, etc.
    • Font size
    • Draw labels (true or false; false means the labels won't be visible)

To encapsulate the above properties, we will have an instance variable for each, the types of which will be:

  • Main window width and height: int
  • Grid units: float
  • All other width, height, length, spacing and weight properties: float
  • All colors: Color
  • Font name: String
  • Font style: int (as determined by the Font class, for example, Font.BOLD)
  • Font size: float
  • Draw (e.g. Should we draw the labels?  Should we draw the minor tic marks?): boolean

Before we start creating variable names let's establish some naming conventions:

Don't forget!!
Speaking of naming conventions, don't forget that there are some general Java naming conventions that you should be following. They include:

  1. Names should not begin with an underscore (_).
  2. Package name should consist of lowercase characters and underscores.
  3. Class and interface names should begin with a capital letter.
  4. Method names should begin with a lowercase character.
  5. Non-constant variable names should begin with a lowercase letter.
  6. Constant variable names should be written in uppercase with underscores separating name components (more about "constant variables," below).
  • Variable names associated directly with grid properties (so far there's only grid units) begin with grid or GRID.
  • Variable names associated with the main window (background color, for example) begin with mw or MW.
  • Variable names associated with the margin begin with margin or MARGIN.
  • Variable names associated with a specific margin contain a reference to their orientation immediately following margin (e.g., marginTop or MARGIN_TOP).
  • Variable names associated with minor tic marks begin with ticMinor or TIC_MINOR.
  • Variable names associated with major tic marks begin with ticMajor or TIC_MAJOR.
  • Variable names associated with grid lines begin with gridLine or GRID_LINE.
  • Variable names associated with the x- or y-axis begin with axis or AXIS.
  • Variable names associated with labels begin with label or LABEL.
  • "Background" is abbreviated bg or BG.
  • "Default value" is abbreviated DV.
  • "Lines per unit" is abbreviated LPU.
  • "Marks per unit" is abbreviated MPU

2. Default Values; Constant Variables

Each of our properties will require a default value. We will encapsulate each default value in a constant variable. Now, where to put our constant variable declarations? One common strategy is to declare them in the class that contains their associated properties, so, for example, BOLD, ITALIC and MONOSPACED are declared in the java.awt.Font class. If you have many such constants, associated with properties across many classes, you might also collect all of them in a single class, as is the case with javax.swing.SwingConstants. We are going to put all of our constants into a class called CPConstants. In later lessons we'll introduce a lot more constants, and we'll put them all in this class. Since they are constant variables we'll follow the convention of giving them all names consisting of nothing but underscores and uppercase characters.

What is a constant variable?
A constant variable is a public class variable (declared static) that cannot be changed after being initialized (declared final). Constant variables serve a special purpose: their values can  be extracted from a class without first loading the class (a time-consuming operation).

Are constant variables ever private?
Constants can certainly be private. In fact, private constants are an excellent idea, since they can be used to replace so-called "magic numbers." For example, instead of writing code like this:

renderAt( 3.3 )

You can write more readable code like this:

private static final float MAXIMUM_PSI = 3.3;
renderAt( MAXIMUM_PSI )

But, if its private, is it a constant variable with a name spelled in uppercase? Well, if it's private, it makes no sense to be able to extract its value from a class without first loading the class. I tell my students if it's public, static and final it's a constant variable, and must be spelled in uppercase. If it's private and feels like a constant (MAXIMUM_PSI, for instance), spell it in uppercase, but feel free to follow the naming convention for non-constant variables.

One more thing about the default values: they will all be implemented as Strings. For example, the default value for font size, a float value, will be implemented as:

String LABEL_FONT_SIZE_DV = "8";

This may seem at first non-intuitive, but it will make more sense later, when we extend our treatment of default values to places like property files, command line arguments and environment variables. To facilitate use, the CPConstants class will contain class methods for converting strings to appropriate types. Here are the string-to-X conversion methods.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
/**
 * Convert a String to an int and return the int.
 * 
 * @param sVal  the String to convert
 * 
 * @return the converted int
 * 
 * @throws  NumberFormatException if sVal
 *          cannot be converted to an int
 */
public static int asInt( String sVal )
{
    int iVal    = Integer.parseInt( sVal );
    return iVal;
}

/**
 * Convert a String to an float and return the float.
 * 
 * @param sVal  the String to convert
 * 
 * @return the converted float
 * 
 * @throws  NumberFormatException if sVal
 *          cannot be converted to a float
 */
public static float asFloat( String sVal )
{
    float fVal    = Float.parseFloat( sVal );
    return fVal;
}

/**
 * Convert a String to a boolean and return the boolean.
 * The operation is case-insensitive.
 * Any value other than "true" is converted to false.
 * 
 * @param sVal  the String to convert
 * 
 * @return the converted boolean
 */
public static boolean asBoolean( String sVal )
{
    boolean bVal    = Boolean.parseBoolean( sVal );
    return bVal;
}

/**
 * Convert a String to a Color and return the Color.
 * The String must be encoded as an integer value.
 * Decimal integer and Hexadecimal integer values
 * are accepted.
 * (A hexadecimal string value begins with "0x" or "#".)
 * 
 * @param sVal  the String to convert
 * 
 * @return the converted Color
 * 
 * @throws  NumberFormatException if sVal
 *          cannot be converted to an integer
 */
public static Color asColor( String sVal )
{
    int     iVal    = Integer.decode( sVal );
    Color   cVal    = new Color( iVal );
    return cVal;
}

/**
 * Convert a String to a font style and return the result.
 * Integer values for font styles are defined in the Font class.
 * Input is case-insensitive; valid values are 
 * PLAIN, BOLD and ITALIC.
 * 
 * @param sVal  the String to convert
 * 
 * @return the converted Color
 * 
 * @throws  IllegalArgumentException if sVal
 *          cannot be converted to a font style.
 */
public static int asFontStyle( String sVal )
{
    String  cisVal  = sVal.toUpperCase();
    int     iVal    = -1;
    switch ( cisVal )
    {
    case "PLAIN":
        iVal = Font.PLAIN;
        break;
    case "BOLD":
        iVal = Font.BOLD;
        break;
    case "ITALIC":
        iVal = Font.ITALIC;
        break;
    default:
        String  err = 
            "\"" + sVal + "\"" + "is not a valid font style";
        throw new IllegalArgumentException( err );
    }
    return iVal;
}

And here are the (many) default value declarations.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
    /////////////////////////////////////////////////
    //   General grid properties
    /////////////////////////////////////////////////
    /** Grid units (pixels-per-unit) default value: float. */
    public static final String  GRID_UNIT_DV        = "65";

    /////////////////////////////////////////////////
    //   Main window properties
    /////////////////////////////////////////////////
    /** Grid units (pixels-per-unit) default value: float. */
    /** Main window width default value: int. */
    public static final String  MW_WIDTH_DV         = "500";
    /** Main window height default value: int. */
    public static final String  MW_HEIGHT_DV        = "500";
    /** Main window background color default value: int. */
    public static final String  MW_BG_COLOR_DV      = "0xE6E6E6";
    
    /////////////////////////////////////////////////
    //   Margin properties
    /////////////////////////////////////////////////
    /** Top margin width default value: float. */
    public static final String  MARGIN_TOP_WIDTH_DV         = "20";
    /** Top background color default value: int. */
    public static final String  MARGIN_TOP_BG_COLOR_DV      = "0x008080";
    /** Right margin width default value: float. */
    public static final String  MARGIN_RIGHT_WIDTH_DV       = "20";
    /** Right margin background color: int. */
    public static final String  MARGIN_RIGHT_BG_COLOR_DV    = "0x008080";
    /** Bottom margin width default value: float. */
    public static final String  MARGIN_BOTTOM_WIDTH_DV      = "60";
    /** Bottom margin background color: int. */
    public static final String  MARGIN_BOTTOM_BG_COLOR_DV   = "0x008080";
    /** Left margin width default value: float*/
    public static final String  MARGIN_LEFT_WIDTH_DV        = "60";
    /** Left margin background color: int. */
    public static final String  MARGIN_LEFT_BG_COLOR_DV     = "0x008080";
    
    /////////////////////////////////////////////////
    //   Tic mark properties
    /////////////////////////////////////////////////
    /** Minor tic mark color default value default value: int. */
    public static final String  TIC_MINOR_COLOR_DV          = "0X000000";
    /** Minor tic mark weight default value: float. */
    public static final String  TIC_MINOR_WEIGHT_DV         = "3";
    /** Minor tic mark length default value: float. */
    public static final String  TIC_MINOR_LEN_DV            = "7";
    /** Minor tic marks per unit default value: float. */
    public static final String  TIC_MINOR_MPU_DV            = "10";
    /** Draw minor tic marks default value: boolean */
    public static final String  TIC_MINOR_DRAW_DV           = "true";
    /** Minor tic mark color default value: int. */
    public static final String  TIC_MAJOR_COLOR_DV          = "0X000000";
    /** Major tic mark weight default value: float. */
    public static final String  TIC_MAJOR_WEIGHT_DV         = "3";
    /** MAJOR tic mark length default value: float. */
    public static final String  TIC_MAJOR_LEN_DV            = "7";
    /** Major tic marks per unit default value: float. */
    public static final String  TIC_MAJOR_MPU_DV            = "1";
    /** Draw major tic marks default value: boolean */
    public static final String  TIC_MAJOR_DRAW_DV           = "true";
    
    /////////////////////////////////////////////////
    //   Grid line properties
    /////////////////////////////////////////////////
    /** Grid line weight default value: float. */
    public static final String  GRID_LINE_WEIGHT_DV     = "1";
    /** Grid lines per unit default value: float. */
    public static final String  GRID_LINE_LPU_DV        = TIC_MAJOR_MPU_DV;
    /** Left margin background color: int. */
    public static final String  GRID_LINE_COLOR_DV      = "0x4B4B4B";
    /** Draw grid lines default value: boolean */
    public static final String  GRID_LINE_DRAW_DV       = "true";

    /////////////////////////////////////////////////
    //   Axis properties
    /////////////////////////////////////////////////
    /** Axis color default value: int. */
    public static final String  AXIS_COLOR_DV          = "0X000000";
    /** Axis weight default value: float. */
    public static final String  AXIS_WEIGHT_DV         = "3";

    /////////////////////////////////////////////////
    //   Label properties
    /////////////////////////////////////////////////
    /** Label font color default value: int. */
    public static final String  LABEL_FONT_COLOR_DV     = "0X000000";
    /** Label font name default value: String. */
    public static final String  LABEL_FONT_NAME_DV      = "Monospaced";
    /**
     *  Label font style default value: String.
     *  One of the Font class constants:
     *  BOLD, ITALIC or PLAIN
     */
    public static final String  LABEL_FONT_STYLE_DV     = "PLAIN";
    /** Label font size default value: float. */
    public static final String  LABEL_FONT_SIZE_DV      = "8";

3. Implementing the Properties

So almost all of our properties will be implemented as instance variables. The two exceptions are the main window default width and height. These are only used in a constructor, and never changed. We'll implement them as class variables. Speaking of constructors, we will also add a default constructor which makes use of the default values (the defalt contructor is a constructor that has parameters; also called a no-argument constructor because you don't have to provide an argument when invoking it). Here are the declarations and the new constructor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    private static final int    mainWindowWidthDV   =
        CPConstants.asInt( CPConstants.MW_WIDTH_DV );
    private static final int    mainWindowHeightDV   =
        CPConstants.asInt( CPConstants.MW_HEIGHT_DV );
    // ...
    public CartesianPlane()
    {
        this( mainWindowWidthDV, mainWindowHeightDV );
    }
    // ...
}

Note that the new constructor makes use of constructor chaining. The code:

this( mainWindowWidthDV, mainWindowHeightDV );

Causes the overloaded constructor CartesianPlane(int, int) to be invoked. Constructor chaining is a convenient way to avoid have to write and maintain the same code in many constructors. If you're going to use constructor chaining the this(...) invocation must be the first line of code in the constructor. Here's a slightly more complex example of constructor chaining:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SmartRect
{
    private static final Color  DEFAULT_COLOR   = Color.BLUE;
    private static final float  DEFAULT_WIDTH   = 127.3f;
    private static final float  DEFAULT_HEIGHT  = 99.6f;
    
    private final Color color;
    private final float width;
    private final float height;
    
    public SmartRect()
    {
        this( DEFAULT_COLOR, DEFAULT_WIDTH, DEFAULT_HEIGHT );
    }
    
    public SmartRect( Color color )
    {
        this( color, DEFAULT_WIDTH, DEFAULT_HEIGHT );
    }
    
    public SmartRect( float width, float height )
    {
        this( DEFAULT_COLOR, width, height );
    }
    
    public SmartRect( Color color, float width, float height )
    {
        this.color = color;
        this.width = width;
        this.height = height;
    }
    // ...
}

Next come the declarations of the instance variables to hold all the other properties:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/////////////////////////////////////////////////
//   Main window properties
//   Note: "width" and "height" are included as
//   main window properties in CPConstants,
//   but it is not necessary to encapsulate
//   their values in instance variables.
//   See the default constructor.
/////////////////////////////////////////////////
private Color   mwBGColor           = 
    CPConstants.asColor( CPConstants.MW_BG_COLOR_DV );

/////////////////////////////////////////////////
//   Margin properties
/////////////////////////////////////////////////
private float   marginTopWidth      =
    CPConstants.asFloat( CPConstants.MARGIN_TOP_WIDTH_DV );
private Color   marginTopBGColor    =
    CPConstants.asColor( CPConstants.MARGIN_TOP_BG_COLOR_DV );
private float   marginRightWidth      =
    CPConstants.asFloat( CPConstants.MARGIN_RIGHT_WIDTH_DV );
private Color   marginRightBGColor    =
    CPConstants.asColor( CPConstants.MARGIN_RIGHT_BG_COLOR_DV );
private float   marginBottomWidth      =
    CPConstants.asFloat( CPConstants.MARGIN_BOTTOM_WIDTH_DV );
private Color   marginBottomBGColor    =
    CPConstants.asColor( CPConstants.MARGIN_BOTTOM_BG_COLOR_DV );
private float   marginLeftWidth      =
    CPConstants.asFloat( CPConstants.MARGIN_LEFT_WIDTH_DV );
private Color   marginLeftBGColor    =
    CPConstants.asColor( CPConstants.MARGIN_LEFT_BG_COLOR_DV );

/////////////////////////////////////////////////
//   Tic mark properties
/////////////////////////////////////////////////
private Color   ticMinorColor       =
    CPConstants.asColor( CPConstants.TIC_MINOR_COLOR_DV );
private float   ticMinorWeight      =
    CPConstants.asFloat( CPConstants.TIC_MINOR_WEIGHT_DV );
private float   ticMinorLen         =
    CPConstants.asFloat( CPConstants.TIC_MINOR_LEN_DV );
private float   ticMinorMPU         =
    CPConstants.asFloat( CPConstants.TIC_MINOR_MPU_DV );
private boolean ticMinorDraw        =
    CPConstants.asBoolean( CPConstants.TIC_MINOR_DRAW_DV );
private Color   ticMajorColor       =
    CPConstants.asColor( CPConstants.TIC_MAJOR_COLOR_DV );
private float   ticMajorWeight      =
    CPConstants.asFloat( CPConstants.TIC_MAJOR_WEIGHT_DV );
private float   ticMajorLen         =
    CPConstants.asFloat( CPConstants.TIC_MAJOR_LEN_DV );
private float   ticMajorMPU         =
    CPConstants.asFloat( CPConstants.TIC_MAJOR_MPU_DV );
private boolean ticMajorDraw        =
    CPConstants.asBoolean( CPConstants.TIC_MAJOR_DRAW_DV );

/////////////////////////////////////////////////
//   Grid line properties
/////////////////////////////////////////////////
private Color   gridLineColor       = 
    CPConstants.asColor( CPConstants.GRID_LINE_COLOR_DV );
private float   gridLineWeight      = 
    CPConstants.asFloat( CPConstants.GRID_LINE_WEIGHT_DV );
private float   gridLineLPU         = 
    CPConstants.asFloat( CPConstants.GRID_LINE_LPU_DV );
private boolean gridLineDraw        =
    CPConstants.asBoolean( CPConstants.GRID_LINE_DRAW_DV );

/////////////////////////////////////////////////
//   Axis properties
/////////////////////////////////////////////////
private Color   axisColor           = 
    CPConstants.asColor( CPConstants.AXIS_COLOR_DV );
private float   axisWeight          = 
    CPConstants.asFloat( CPConstants.AXIS_WEIGHT_DV );

/////////////////////////////////////////////////
//   Label properties (these are the labels that
//   go on the x- and y-axes, e.g., 1.1, 1.2)
/////////////////////////////////////////////////
private Color   labelFontColor      =
    CPConstants.asColor( CPConstants.LABEL_FONT_COLOR_DV );
private String  labelFontName       =
    CPConstants.LABEL_FONT_NAME_DV;
private int     labelFontStyle      =
    CPConstants.asFontStyle( CPConstants.LABEL_FONT_STYLE_DV );
private float   labelFontSize       = 
    CPConstants.asFloat( CPConstants.LABEL_FONT_SIZE_DV );

Occasionally our users are going to want to ask what the current value of a property is, or even change the value of a property. Since our instance variables are private (as they should be!) we will need methods to provide access to them. By convention, a setter is a method used to change a property value, and a getter is a method used to get the value (collectively these are called accessors). The format of setters and getters follows a simple a simple pattern. Given a property prop of a given type (int, float, etc.) a setter has this declaration:

public void setProp( type prop )

and a getter looks like this:

public type getProp()

Here are examples based on our top margin width property:

1
2
3
4
5
6
7
8
9
    public float getMarginTopWidth()
    {
        return marginTopWidth;
    }

    public void setMarginTopWidth(float marginTopWidth)
    {
        this.marginTopWidth = marginTopWidth;
    }

Exceptions to the pattern can be made for boolean property getters. While the getProperty pattern is acceptable, you can substitute is for get. Here are the setter and getter for one of our boolean properties.

1
2
3
4
5
6
7
8
9
public boolean isGridLineDraw()
{
    return gridLineDraw;
}

public void setGridLineDraw(boolean gridLineDraw)
{
    this.gridLineDraw = gridLineDraw;
}

A couple of more notes about properties:

Note 1: Not every property is encapsulated in an instance variable.
While it is true that every property in this project (at least so far) is associated with an instance variable, that is not always true. For example area and perimeter are properties of a rectangle, but they are not typically stored in variables:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public float getArea()
{
    float   area    = width * height;
    return area;
}

public float getPerimeter()
{
    float   perimeter   = 2 * width + 2 * height;
    return perimeter;
}

Note 2: Not every property has a getter and a setter.
There are read-only properties that have only getters (such as area and perimeter in the above example). It is also possible (if rare) to have a write-only property that has s setter but no getter.

Note 3: Not every instance (or class) variable represents a public property.
Some of the instance variables we have so far, for example, currWidth and currHeight, exist purely for the convenience of out code and are of no interest to our users.

Note 4: Completing the setter/getter pattern.
There's one more bit about setters and getters that we haven't discussed: setters and getters for properties implemented as arrays. Such properties often have 2 pairs of setters and getters: one pair to set/get the entire array, and another pair to set/get an element in the array. The setter for an array element has a second parameter that describes the index of the element to set; the getter has a parameter that describes the element to get. Here is an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
private double[]    results;
public double[] getResults()
{
    return results;
}
 
public void setResults(double[] results)
{
    this.results = results;
}

public double getResult( int index )
{
    return results[index];
}
    
public void setResult( double value, int index )
{
    results[index] = value;
}

If you're not looking forward to writing the setter and getter for every one of our properties, you're in luck. Chances are your IDE will do it for you. If you're using Eclipse, for instance, pull down the source menu and select Generate Setters and Getters. In the resulting dialog, choose the variables for which you want setters and getters; if you wish, for a given variable you can select just a setter or just a getter. I also suggest you check the Generate Method Comments toggle button; we haven't started talking about documentation yet,  but once we do this will save a lot of typing. Then poke the generate button.

Here are some of the results for our Cartesian Plane project.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    // ...
    /**
     * @return the ticMajorColor
     */
    public Color getTicMajorColor()
    {
        return ticMajorColor;
    }

    /**
     * @param ticMajorColor the ticMajorColor to set
     */
    public void setTicMajorColor(Color ticMajorColor)
    {
        this.ticMajorColor = ticMajorColor;
    }

    /**
     * @return the ticMajorWeight
     */
    public float getTicMajorWeight()
    {
        return ticMajorWeight;
    }

    /**
     * @param ticMajorWeight the ticMajorWeight to set
     */
    public void setTicMajorWeight(float ticMajorWeight)
    {
        this.ticMajorWeight = ticMajorWeight;
    }

    /**
     * @return the ticMajorLen
     */
    public float getTicMajorLen()
    {
        return ticMajorLen;
    }

    /**
     * @param ticMajorLen the ticMajorLen to set
     */
    public void setTicMajorLen(float ticMajorLen)
    {
        this.ticMajorLen = ticMajorLen;
    }

    /**
     * @return the ticMajorMPU
     */
    public float getTicMajorMPU()
    {
        return ticMajorMPU;
    }

    /**
     * @param ticMajorMPU the ticMajorMPU to set
     */
    public void setTicMajorMPU(float ticMajorMPU)
    {
        this.ticMajorMPU = ticMajorMPU;
    }

    /**
     * @return the ticMajorDraw
     */
    public boolean isTicMajorDraw()
    {
        return ticMajorDraw;
    }

    /**
     * @param ticMajorDraw the ticMajorDraw to set
     */
    public void setTicMajorDraw(boolean ticMajorDraw)
    {
        this.ticMajorDraw = ticMajorDraw;
    }
    // ...

4. Breaking a Task into Manageable Units

You never want to mash hundreds (or tens or thousands) of lines of code into a small space in order to accomplish multiple tasks at once. Suppose you go to your paintComponent method and try to write one long stream of code to draw every bit of your plane. Afterward you find it doesn't work quite right. Do you have an error in your code to draw the grid lines? Or maybe the tic marks? If your labels don't look right how are you going to refine the label drawing code if it's mashed together with all the rest of your code?

We've already encapsulated some of our tasks in helper methods, for example, to draw the grid lines and paint the margins. Eventually we will also break out the code for drawing the axes, tic marks, labels and (several lessons from now) the user's graphs. For now, let's write the code for drawing the axes (at last! some visible progress!). We will also have to adapt the existing code for drawing grid lines and painting margins to use our new variable names. Here's the code to do that.

It's a common saying among object-oriented programmers that "a class should do one thing and do it well." But that can be applied to every other level of programming, too. A system should do one thing and do it well; a package should do one thing and do it well; a method, even a simple loop should do one thing and do it well.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public void paintComponent( Graphics graphics )
{
    // ...
    drawGrid();
    drawAxes();
    paintMargins();
    // ...
}

private void drawGrid()
{
    gtx.setColor( gridLineColor );
    gtx.setStroke( new BasicStroke( gridLineWeight ) );
    float   gridSpacing = gridUnit / gridLineLPU;
    
    float   numLeft = (float)Math.floor( gridWidth / 2 / gridSpacing );
    float   leftXco = centerXco - numLeft * gridSpacing;
    for ( float xco = leftXco ; xco <= maxXco ; xco += gridSpacing )
    {
        Line2D  gridLine    = 
            new Line2D.Float( xco, minYco, xco, maxYco );
        gtx.draw( gridLine );
    }
    
    float   numTop  = (float)Math.floor( gridHeight / 2f / gridSpacing );
    float   topYco  = centerYco - numTop * gridSpacing;
    for ( float yco = topYco ; yco <= maxYco ; yco += gridSpacing )
    {
        Line2D  gridLine    = 
            new Line2D.Float( minXco, yco, maxXco, yco );
        gtx.draw( gridLine );
    }
}

private void drawAxes()
{
    gtx.setColor( axisColor );
    gtx.setStroke( new BasicStroke( axisWeight ) );
    Line2D  line    = new Line2D.Float();
    
    // x axis
    line.setLine( centerXco, minYco, centerXco, maxYco );
    gtx.draw( line );
    
    // y axis
    line.setLine( minXco, centerYco, maxXco, centerYco );
    gtx.draw( line );
}

private void paintMargins()
{
    Rectangle2D rect    = new Rectangle2D.Float();
    
    // top margin
    rect.setRect( 0, 0, currWidth, marginTopWidth );
    gtx.setColor( marginTopBGColor );
    gtx.fill( rect );
    
    // right margin
    float   marginRightXco  = currWidth - marginRightWidth;
    rect.setRect( marginRightXco, 0, marginRightWidth, currHeight );
    gtx.setColor( marginRightBGColor );
    gtx.fill( rect );
    
    // bottom margin
    float   marginBottomXco  = currHeight - marginBottomWidth;
    rect.setRect( 0, marginBottomXco, currWidth, marginBottomWidth );
    gtx.setColor( marginBottomBGColor );
    gtx.fill( rect );
    
    // left margin
    rect.setRect( 0, 0, marginLeftWidth, currHeight );
    gtx.setColor( marginLeftBGColor );
    gtx.fill( rect );
}

Next: The Cartesian Plane Project, Tic Marks, Labels... and more

No comments:

Post a Comment