The question should not be about duplicating code through the creation of multiple accessor methods. The question should be about what behaviours you want to assign to your classes.
public class PhoneNumber {
private String number;
private String extension;
private DateTime lastCalled;
public String getNumber() {
return this.number;
}
public String getExtension() {
return this.extension;
}
public DateTime getLastCalled() {
return this.lastCalled;
}
}
There are many problems with the code, including:
- Breaking encapsulation.
- Lending itself to breaking the Law of Demeter.
- Introducing a dependency between clients of this class and
DateTime
.
- Brittleness.
- Not being object-oriented.
Breaking Encapsulation
If you think of classes in terms of a capsule, the inner workings of that capsule should remain unknown to its clients. The data types that PhoneNumber
uses should not impose extra dependencies on its clients. What happens of you wanted to rewrite PhoneNumber
to use something other than String
s or DateTime
instances? All your clients have to change, as well, and that is a fancy term for unmaintainable code.
Law of Demeter
The following code becomes trivial to write (and it will be written):
public void printPhoneNumber( PhoneNumber pn ) {
System.out.println( pn.getLastCalled().getTime() );
}
This introduces a potential NullPointerException
. It can be rewritten:
public void printPhoneNumber( PhoneNumber pn ) {
printDateTime( pn.getLastCalled() );
}
public void printDateTime( DateTime dateTime ) {
System.out.println( dateTime.getTime() );
}
This avoids breaking the Law of Demeter
, but the NullPointerException
problem remains. Since you want to avoid duplicating code, the last thing you want to do is codify it as:
public void printDateTime( DateTime dateTime ) {
if( dateTime != null ) {
System.out.println( dateTime.getTime() );
}
}
Because that only exacerbates the problem of duplicated code (if( dateTime != null )
).
Imposed Dependency
Imagine you have three classes: A
, B
, and C
. A
uses B
and C
uses A
. Why should C
, then, be burdened with the knowledge of class B
? Yet this is exactly what has been done by exposing the DateTime
class through the getLastCalled()
accessor.
Brittle
What happens when you want to add area code to the PhoneNumber
? What about dialing prefixes? What about pauses to get to an external line? What about long distance numbers? What if you wanted to ensure that certain numbers remain undisclosed (e.g., unlisted numbers)?
While these features might lie beyond the problem domain, by exposing the inner workings of the class in such a fashion, you have indirectly introduced a tight coupling of the PhoneNumber
class with system. That is, any clients of the PhoneNumber
class now know that a PhoneNumber consists of a number and extension. What if three or four different classes were displaying, saving, printing, or transmitting a PhoneNumber
on behalf of the user?
If you add the area code, you now have four places to change.
What if you wanted to have a history of the last called numbers?
Again, and again the clients of the class are now broken.
Not Object-Oriented
Object-orientation is about behaviours, first and foremost, with attributes being ancillary -- almost an after thought. Read the article Tell, Don't Ask.
Implementation
There is another way. Describe how the PhoneNumber
should behave. What operations should it, if you pardon the word-play, answer?
public class PhoneNumber extends Observable {
private String line;
private String external;
private String prefix;
private String countryCode;
private String areaCode;
private String digits;
private String extension;
public Call call() {
// Inform observers that a telephone call was made.
//
notifyObservers();
}
public String toString() {
// Return a human-friendly version of this telephone number.
//
}
}
public class LittleBlackBook implements Observer {
private AddressBook numbers;
private List<CallHistory> callHistory;
public void write( PhoneNumber number ) {
getNumbers().add( number );
number.addObserver( this );
}
private List<PhoneNumber> getNumbers() {
if( this.numbers == null ) {
this.numbers = createAddressBook();
}
return this.numbers;
}
protected AddressBook createAddressBook() {
return new AddressBook();
}
// Implement the update method ...
}
public class CallHistory {
private PhoneNumber pn;
private DateTime called;
}
Decoupling the knowledge of what information is inside a PhoneNumber from how the underlying implementation works opens up so many more possibilities.