Principle 3.3.1. EFFECTIVE DESIGN: Constructors.
Constructors provide a flexible way to initialize an object’s instance variables when the object is created.
public
. Here is a simple constructor for our OneRowNim
class. Notice there is no return type void in between the public and the classname.public OneRowNim()
{ nSticks = 7;
player = 1;
}
nSticks
and player
. In our current version of OneRowNim
these variables are given initial values by using initializer statements when they are first declared:private int nSticks = 7;
private int player = 1;
OneRowNim
class it doesn’t really matter which way we do it. However, the constructor provides more flexibility because it allows the state of the object to be initialized at runtime. Of course, it would be somewhat redundant (though permissible) to initialize the same variable twice, once when it is declared and again in the constructor, so we should choose one or the other way to do this. For now, let’s stick with initializing the instance variables when they are declared.new
followed by the constructor invocation:OneRowNim game // Declare
= new OneRowNim(); // and instantiate game1
OneRowNim game2 // Declare
= new OneRowNim(); // and instantiate game2
OneRowNim
object, what initializations could be performed? One initialization that would seem appropriate is to initialize the initial number of sticks to a number specified. In order to do this, we would need a constructor with a single int
parameter:public OneRowNim(int sticks)
{ nSticks = sticks;
}
OneRowNim
:OneRowNim game1 = new OneRowNim(21);
OneRowNim game2 = new OneRowNim(13);
setSticks()
method that was discussed briefly in the last section. The difference is that we can now set the number of sticks when we create the object.setSticks()
method or keep both in our class definition? The constructor can only be invoked as part of a new
statement when the object is created, but the setSticks()
method could be called anytime we want. In many cases, having redundant methods for doing the same task in different ways would be an asset, because it allows for more flexibility in how the class could be used. However, for a game like One Row Nim, a major concern is that the two instance variables get changed only in a manner consistent with the rules for One Row Nim. The best way to guarantee this is to have takeSticks()
as the only method that changes the instance variables nSticks
and player
. The only time that it should be possible to set the number of sticks for a game is when a constructor is used to create a new instance of OneRowNim
.public void OneRowNim(int sticks)
{ nSticks = sticks;
}
OneRowNim(int sticks)
constructor so that it sets the number of sticks and also have it also set player two as the player who takes the first turn. Also, write a constructor that takes 2 parameters to set the number of sticks and to set the player.OneRowNim(int sticks)
constructor to also set the player to 2. Write another constructor that takes 2 parameters to set the number of sticks and to set the player.public
, the default constructor will also be public
and, hence, accessible to other objects.OneRowNim
would be equivalent to a public
constructor method with an empty body:public OneRowNim() { }
OneRowNim
contained no explicit definition of a constructor:OneRowNim game = new OneRowNim();
OneRowNim
constructors:public OneRowNim() {} // Constructor #1
public OneRowNim(int sticks) // Constructor #2
{ nSticks = sticks;
}
OneRowNim
object. Having multiple constructors lends flexibility to the design of a class. In this case, the first constructor merely accepts OneRowNim
’s default initial state. The second enables the user to initialize the number of sticks to something other than the default value.OneRowNim
is used as the name for two distinct constructor methods. What distinguishes one constructor from another is its signature, which consists of its name together with the number and types of formal parameters it takes. Thus, our OneRowNim
constructors have the following distinct signatures:OneRowNim()
OneRowNim(int)
int
parameter.public OneRowNim(int sticks, int starter)
{ nSticks = sticks; // Set the number of sticks
player = starter; // Set who starts
}
OneRowNim game3 = new OneRowNim(14, 2);
OneRowNim game4 = new OneRowNim(31, 1);
OneRowNim
game that starts with 2 sticks and has player 14 as the player with the first move.OneRowNim
class. Each constructor has the name OneRowNim
, but each has a distinct signature:OneRowNim()
OneRowNim(int)
OneRowNim(int, int)
new
expression when an instance object is first created. Each of these is a valid invocation of a OneRowNim
constructor:// Default constructor
OneRowNim game1 = new OneRowNim();
// Sets number of sticks
OneRowNim game2 = new OneRowNim(21);
// Sets both instance variables
OneRowNim game3 = new OneRowNim(19, 2);
// No matching constructors
OneRowNim game4 = new OneRowNim("21");
OneRowNim game5 = new OneRowNim(12, 2, 5);
String
parameter, so there’s no matching constructor. In the second case, there is no constructor that takes three int
arguments. In both cases, the Java compiler would complain that there is no constructor method that matches the invocation.