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.