Object-oriented
Object-oriented programming is about asking objects to do something: a deceptively difficult concept to correctly apply.
Goban
Consider a 2D game board, like for playing Go (called a goban).
Think first about the behaviour it requires to accomplish its task. This means listing the behaviour for an object rather than deciding on data the behaviours manipulate. For example a basic board might have the following behaviours:
- Place a Go stone.
- Remove a Go stone.
- Remove all the stones.
For a computer version of Go, it is convenient to bring attention to specific areas:
- Mark an intersection (e.g., triangle, number, letter, circle, square).
- Remove a mark from a marked intersection.
- Remove all the marks.
Notice that a goban does not need to provide a way to provide clients with a reference to the stone at a specific intersection. Instead, it can answer questions about its state. For example, a goban might answer the following questions:
- Is there a black stone at a given intersection?
- Is there a white stone at a given intersection?
- Is there a mark at a given intersection?
It is not the responsibility of the goban to know the state of the game: that belongs to an instance of a Game (which has Rules). In real life, a goban is simply a stage for stones.
At this point, we could write an interface for a goban without knowing how the underlying implementation will work.
public interface Goban {
public void place( Stone stone, Point point );
public void removeStone( Point point );
public void removeStones();
public void place( Mark mark, Point point );
public void removeMark( Point point );
public void removeMarks();
public boolean hasWhiteStone( Point point );
public boolean hasBlackStone( Point point );
public boolean hasMark( Point point );
}
Notice how the board is cleanly separated from both Rules and Games. This makes the goban reusable for other games (involving stones and intersections). The goban could inherit from a generic interface (e.g., a Board interface), but this should suffice to explain one way to think in terms of objects.
Encapsulation
An implementation of the Goban interface does not expose its internal data. At this point, I could ask you to implement this interface, write unit tests, and send me the compiled class when you have finished.
I do not need to know what data structures you have used. I can use your implementation to play on (and depict) a Goban. This is a crucial point that many projects get wrong. Many, many projects code the following:
public class Person {
private HairColour hairColour = new HairColour( Colour.BROWN );
public Person() {
}
public HairColour getHairColour() {
return hairColour;
}
public void setHairColour( HairColour hairColour ) {
this.hairColour = hairColour;
}
}
This is ineffective encapsulation. Consider the case where Bob does not like to have his hair coloured pink. We can do the following:
public class HairTrickster {
public static void main( String args[] ) {
Person bob = new Person();
HairColour hc = bob.getHairColour();
hc.dye( Colour.PINK );
}
}
Bob has now had his hair coloured pink, and nothing could prevent it. There are ways to avoid this situation, but people do not do them. Instead, encapsulation is broken resulting in rigid, inflexible, bug-ridden, and unmaintainable systems.
One possible way to enforce encapsulation is by returning a clone of HairColour
. The revised Person class now makes it difficult to change the hair colour to Pink.
public class Person {
private HairColour hairColour = new HairColour( Colour.BROWN );
public Person() {
}
public HairColour getHairColour() {
return hairColour.clone();
}
public void setHairColour( HairColour hairColour ) {
if( !hairColour.equals( Colour.PINK ) {
this.hairColour = hairColour;
}
}
}
Bob can sleep soundly, knowing he will not awake to a pink dye job.