Lab: Questions About Information Hiding
Instructions:
Answer the following questions one at a time. After answering each question,
check your answer (by clicking on the check-mark icon if it is available)
before proceeding to the next question.
Getting Ready:
Before going any further, you should:
-
Depending on your development environment, create either a
directory or a project for this lab.
-
Setup your development environment.
-
Download the following files:
to an appropriate directory/folder. (In most browsers/OSs, the
easiest way to do this is by right-clicking/control-clicking on
each of the links above.)
1. Updating a Test Harness:
Recall that in an earlier lab you developed a test harness called
Test
. For this part of the lab you will update that class.
-
Modify
Test.java
so that it uses
the System.out
object rather than
the JMUConsole
class. (Hint: Use Find/Replace.)
-
Add a
forEqualString()
method to the Test
class
that can be used to test for the equality of an expected String
object and an actual String
object. What code did you add?
solution/Test.java
(Fragment: forEqualString)
/**
* Display an alert if the contents of the actual String object
* is not not equal to the contents of the expected value String object.
*
* @param description A description of the test
* @param expected The expected value
* @param actual The actual value
*/
public static void forEqualString(String description,
String expected, String actual) {
if (!expected.equals(actual)) {
System.out.printf("%s Expected: %s, Actual: %s\n",
description, expected, actual);
}
}
2. Creating a Simple Class:
This part of the lab will give you some more experience creating classes.
-
Read the design specification for the
JMUPhoneNumber
class.
-
Before writing the class, write a class named
JMUPhoneNumberDriver
that uses
the Test
class to test the default constructor,
setFormat()
and toString()
methods.
(Writing tests before writing the class is called test-driven
development or TDD.)
solution/JMUPhoneNumberDriver.java
(Fragment: 0)
public class JMUPhoneNumberDriver
{
public static void main(String[] args)
{
JMUPhoneNumber phone;
String actual, expected;
phone = new JMUPhoneNumber();
phone.setFormat(JMUPhoneNumber.US_OLD);
expected = "(540) 568-6211";
actual = phone.toString();
Test.forEqualString("US_OLD", expected, actual);
phone.setFormat(JMUPhoneNumber.US_NEW);
expected = "540-568-6211";
actual = phone.toString();
Test.forEqualString("US_NEW", expected, actual);
phone.setFormat(JMUPhoneNumber.EUROPE);
expected = "540.568.6211";
actual = phone.toString();
Test.forEqualString("EUROPE", expected, actual);
phone.setFormat(JMUPhoneNumber.JMU);
expected = "x8-6211";
actual = phone.toString();
Test.forEqualString("JMU", expected, actual);
}
}
-
Implement the default constructor,
setFormat()
and toString()
methods
in the JMUPhoneNumber
class (based on the SRS).
-
What code did you write (for the
JMUPhoneNumber
class)?
solution/JMUPhoneNumber.java
/**
* An encapsulation of a phone number at JMU
*
* @author Prof. David Bernstein, James madison University
* @version 1.0
*/
public class JMUPhoneNumber
{
private int areaCode, exchange, extension, format;
public static final int US_OLD = 0;
public static final int US_NEW = 1;
public static final int EUROPE = 2;
public static final int JMU = 3;
public static final int OFFICE = 568;
public static final int DORM = 612;
/**
* Default Constructor
*
* Constructs a JMUPhoneNumber with the areaCode set to 540,
* the exchange set to 568, the extension set to 6211, and the
* format set to JMU
*/
public JMUPhoneNumber()
{
areaCode = 540;
exchange = 568;
extension = 6211;
format = JMU;
}
/**
* Returns true iff this JMUPhoneNumber has the same area code,
* exchange, and extension as the given JMUPhoneNumber.
*
* @param other The JMUPhoneNumber to compare with this one
* @return true if the two have the same attributes; false otherwise
*/
public boolean equals(JMUPhoneNumber other)
{
return (this.areaCode == other.areaCode)
&& (this.exchange == other.exchange)
&& (this.extension == other.extension);
}
/**
* Checks if this JMUPhoneNumber is in a dorm
*
* @return true if in a dorm and false otherwise
*/
public boolean isDorm()
{
return (exchange == DORM);
}
/**
* Checks if this JMUPhoneNumber is in an office
*
* @return true if in an office and false otherwise
*/
public boolean isOffice()
{
return (exchange == OFFICE);
}
/**
* Sets the exchange associated with this JMUPhoneNumber
*
* Note: If the parameter is not valid this method sets the
* exchange to OFFICE
*
* @param exchange The exchange (either OFFICE or DORM)
*/
public void setExchange(int exchange)
{
if (exchange == DORM) this.exchange = DORM;
else this.exchange = OFFICE;
}
/**
* Sets the extension associated with this JMUPhoneNumber
*
* Note: If the parameter is not valid this method sets the
* extension to 6211
*
* @param extension The extension (between 0000 and 9999)
*/
public void setExtension(int extension)
{
if ((extension >= 0) && (extension <= 9999))
this.extension = extension;
else
this.extension = 6211;
}
/**
* Sets the format to be used by the toString method
*
* The valid formats along with examples are:
*
* US_OLD (540) 568-6211
* US_NEW 540-568-6211
* EUROPE 540.568.6211
* JMU x8-6211
*
* Note: If the parameter is not valid this method sets the
* format to JMU
*
* @param format A valid format
*/
public void setFormat(int format)
{
if (format == US_OLD) this.format = US_OLD;
else if (format == US_NEW) this.format = US_NEW;
else if (format == EUROPE) this.format = EUROPE;
else this.format = JMU;
}
/**
* Returns a formatted String representation of this JMUPhoneNumber
* (see the discussion of the setFormat method)
*/
public String toString()
{
String fourDigitExtension, result;
fourDigitExtension = String.format("%04d",extension);
result = "";
if (format == US_OLD)
{
result = "("+areaCode+") "+exchange+"-"+fourDigitExtension;
}
else if (format == US_NEW)
{
result = areaCode+"-"+exchange+"-"+fourDigitExtension;
}
else if (format == EUROPE)
{
result = areaCode+"."+exchange+"."+fourDigitExtension;
}
else if (format == JMU)
{
if (exchange == OFFICE) result = "x8-";
else if (exchange == DORM) result = "x2-";
result += fourDigitExtension;
}
return result;
}
}
-
Test these methods using your driver.
-
One method at a time, implement and test the rest of the
JMUPhoneNumber
class.
-
What code did you add to the
JMUPhoneNumber
class?
solution/JMUPhoneNumber.java
(Fragment: 1)
/**
* Checks if this JMUPhoneNumber is in a dorm
*
* @return true if in a dorm and false otherwise
*/
public boolean isDorm()
{
return (exchange == DORM);
}
/**
* Checks if this JMUPhoneNumber is in an office
*
* @return true if in an office and false otherwise
*/
public boolean isOffice()
{
return (exchange == OFFICE);
}
/**
* Sets the exchange associated with this JMUPhoneNumber
*
* Note: If the parameter is not valid this method sets the
* exchange to OFFICE
*
* @param exchange The exchange (either OFFICE or DORM)
*/
public void setExchange(int exchange)
{
if (exchange == DORM) this.exchange = DORM;
else this.exchange = OFFICE;
}
/**
* Sets the extension associated with this JMUPhoneNumber
*
* Note: If the parameter is not valid this method sets the
* extension to 6211
*
* @param extension The extension (between 0000 and 9999)
*/
public void setExtension(int extension)
{
if ((extension >= 0) && (extension <= 9999))
this.extension = extension;
else
this.extension = 6211;
}
-
What code did you add to the driver?
solution/JMUPhoneNumberDriver.java
(Fragment: 1)
// Valid DORM exchange
phone.setExchange(JMUPhoneNumber.DORM);
Test.forTrue("Valid DORM - isDorm()", phone.isDorm());
Test.forFalse("Valid Dorm - isOffice()", phone.isOffice());
expected = "x2-6211";
actual = phone.toString();
Test.forEqualString("JMU Dorm", expected, actual);
// Invalid exchange
phone.setExchange(999);
Test.forFalse("Invalid Exchange - isDorm()", phone.isDorm());
Test.forTrue("Invalid Exchange - isOffice()", phone.isOffice());
expected = "x8-6211";
actual = phone.toString();
Test.forEqualString("JMU DORM", expected, actual);
// Valid OFFICE exchange
phone.setExchange(JMUPhoneNumber.OFFICE);
Test.forFalse("Valid OFFICE - isDorm()", phone.isDorm());
Test.forTrue("Valid OFFICE - isOffice()", phone.isOffice());
expected = "x8-6211";
actual = phone.toString();
Test.forEqualString("JMU OFFICE", expected, actual);
// Invalid Extension
phone.setExtension(-1);
expected = "x8-6211";
actual = phone.toString();
Test.forEqualString("Invalid Extension (0)", expected, actual);
phone.setExtension(10000);
expected = "x8-6211";
actual = phone.toString();
Test.forEqualString("Invalid Extension (10000)", expected, actual);
// Valid Extension
phone.setExtension(1234);
expected = "x8-1234";
actual = phone.toString();
Test.forEqualString("Valid Extension", expected, actual);
-
Make sure that you KEEP THIS CLASS and the driver.
You'll need them for other labs.
3. Review:
This part of the lab will help you remember some important things
about creating classes.
-
Your
setExchange()
method should look like the following:
public void setExchange(int exchange)
{
this.exchange = exchange;
}
If not, fix it.
-
What happens (when you compile and/or execute your code)
if you change it to the following?
public void setExchange(int exchange)
{
exchange = exchange;
}
It compiles but when executed the attribute named exchange
is not changed. The parameter named exchange
has its value
assigned to it (i.e., it doesn't change either).
-
What happens (when you compile and/or execute your code)
if you change it to the following?
public void setExchange(int exchange)
{
exchange = this.exchange;
}
It compiles but when executed the attribute named exchange
is not changed. The value of the attribute named exchange
is assigned to the parameter named exchange
but, since
parameters are passed by value in Java, this has no impact outside of
this method.
-
What happens (when you compile and/or execute your code)
if you change it to the following?
public void setExchange(int this.exchange)
{
exchange = this.exchange;
}
It does not compile.
-
What happens (when you compile and/or execute your code)
if you change it to the following?
public void setExchange(int exchange)
{
this.exchange = this.exchange;
}
It compiles and the value of the attribute named exchange
is assigned to the attribute named exchange
. Which is to say,
it doesn't change.
-
Change your
setExchange()
method back to:
public void setExchange(int exchange)
{
this.exchange = exchange;
}
4. The Benefits of Information Hiding:
This part of the lab will help you understand some of the benefits
of information hiding.
-
In your driver, after declaring and constructing a
JMUPhoneNumber
named csOffice
, add the
following line:
csOffice.extension = 2745;
-
Compile your driver.
-
What happens?
The driver does not compile.
-
Remove the offending code from your driver.
-
What is gained by making the
areaCode
, exchange
,
and extension
private?
It prevents invalid area codes and exchanges.
-
What is gained by making
isDorm
and isOffice
public methods rather than public attributes?
Consistency is maintained. In other words, it prevents the two attributes
from both being true
or both being flase
(which, obviously, doesn;t make sense).
-
Should the
toString()
method create a String
each time it is called, or should it return an attribute?
This is an important question to ask about many accessors (and related
methods). In general, you can "do the work" every time the method is called
or you can "do the work" only when something changes. There are advantages
and disadvantages to both, and you need to think about the
details of the situation in each case.
In this case, I would create a String
each time the
toString()
method is called. Though this means "the work"
is being done multiple times, even if the attributes haven't changed,
I think that is a cost that is worth incurring.
If you do it the other way, the attribute containing the String
representation will have to be modified every time the
setFormat()
, setExchange()
, and
setExtension()
methods are called. In my opinion, this
would be prone to errors.
-
Suppose you return an attribute in the
toString()
method.
Should the attribute be public or private? Why?
It should definitely be private. Otherwise the String
could be changed outside of the class (and be inconsistent with the desired
format).
-
Why would it be useful to have a method named
equals()
in the JMUPhoneNumber
class?
Because we might want to compare two JMUPhoneNumber
objects
and the ==
operator only compares references.
-
Why is it better to have a method named
equals()
than to
have accessors for all of the attributes?
Because there is no reason for anyone else to know the details
of JMUPhoneNumber
objects. Again, this is an important
aspect of information hiding.
-
Should the
equals()
method compare the format?
I don't think so. The format is not really an intrinsic attribute of
JMUPhoneNumber
objects. However, even if you decide
otherwise, this is not something that anyone other than the designer
of the class should have to think about (i.e., it is an internal
detail that should be hidden).
-
Implement the
equals()
method. What code did you add?
solution/JMUPhoneNumber.java
(Fragment: equals)
/**
* Returns true iff this JMUPhoneNumber has the same area code,
* exchange, and extension as the given JMUPhoneNumber.
*
* @param other The JMUPhoneNumber to compare with this one
* @return true if the two have the same attributes; false otherwise
*/
public boolean equals(JMUPhoneNumber other)
{
return (this.areaCode == other.areaCode)
&& (this.exchange == other.exchange)
&& (this.extension == other.extension);
}
-
Add one or more tests to your
JMUPhoneNumberDriver
class
that test the equals()
method. What code did you add?
solution/JMUPhoneNumberDriver.java
(Fragment: equals)
// equals()
JMUPhoneNumber a, b, c, d, e;
a = new JMUPhoneNumber();
b = new JMUPhoneNumber();
Test.forTrue("equals() - Same", a.equals(b));
c = new JMUPhoneNumber();
c.setExchange(JMUPhoneNumber.DORM);
Test.forFalse("equals() - Different Exchange", a.equals(c));
d = new JMUPhoneNumber();
d.setExtension(1234);
Test.forFalse("equals() - Different Extension", a.equals(d));
e = new JMUPhoneNumber();
e.setExchange(JMUPhoneNumber.DORM);
e.setExtension(8888);
Test.forFalse("equals() - Different Both", a.equals(e));
-
Why are the "constants" in the
JMUPhoneNumber
class
declared to be public
?
So that they can be used in other classes (without having to know their
values).
-
Why isn't it a problem that the "constants" are
declared to be
public
?
Because they are also declared to be final
. So, they can't
be "damaged".
-
Given that the
JMUPhoneNumber
class has a
setExtension()
method, some people would argue that the
extension
attribute should be made public. Why is this
argument not even slightly compelling?
Because the class does not have a getExtension()
method.
So, making the extension
attribute public would change the
functionality provided by the class.
-
Suppose the
JMUPhoneNumber
class also had a
getExtension()
method. Would you then argue that
the extension
attribute should be public?
No. Notice that the setExtension()
method prevents the caller from
assigning a nonsensical value to the extension
attribute.
If the extension
attribute were public, it could be assigned
any int
value (even one that isn't a valid extension).
5. Other Questions:
This part of the lab will help you think about some other important issues
that arise when designing/developing classes.
-
Why is it a good idea to make immutable attributes
final
even when they are private and the class does not include any
public mutators?
Because then they can't be changed inadvertently by other methods in the class.
-
Why would it be useful to have a method named
parseString()
that converts a String
representation (e.g., "540-568-1671") to a JMUPhoneNumber
?
Because it is much easier to ask people to enter a phone number as a
String
?
-
Should such a
parseString()
method belong to
the String
class or the JMUPhoneNumber
class?
The JMUPhoneNumber
class. Otherwise, the
String
class would have to know about all possible
classes and their String
representations.
-
An auto-dialer iteratively calls all of the phone numbers at a location.
How would you implement a
next()
method that would facilitate
this?
It would increase the extension by 1 (until it got to 9999), construct
a new JMUPhoneNumber
with that extension, and then return
it. If the increased extension is greater than 9999 it needs to use
the next exchange (if there is one) and make the extension 0001.
-
Are
JMUPhoneNumber
objects mutable or immutable?
They are mutable since their attributes can be changed.
-
Should
JMUPhoneNumber
objects be mutable or immutable?
The answer to this question depends on how they are going to be used.
I think that, in most cases, they should probably be
immutable. However, if a JMUPhoneNumber
was being used in
an application in which it was an attribute of a phone, you might want
it to be mutable (since phone numbers can change). This is a question
that will be discussed in more detail in other courses.