Class 7: Principles of Object Orientation: Encapsulation


Public/Private
We've been using the public keyword without understanding it for quite a while. Now we'll get into it a bit. Every method (aka function) or data member is tagged as either public, private or protected. We're not ready to discuss protected, but understanding the other two is easy. Here's the rule:
Anything marked private is not visible/accessible outside of the class. Anything marked public is visible/accessible outside of the class.
So that's the mechanics of public/private in a nutshell. But what's the purpose of using this?

Interface vs. Implementation
As we've discussed many times: one of the ideas that permeates computer science is the separation of interface from implementation. Interface is, essentially, what you need to know to use a thing. Implementation is how the thing actually works. In procedural programming, programs are made up of functions. A function's interface is its prototype, along with (perhaps) a bit of documentation about what the function's purpose is. The implementation is the function's body (or definition). Scoping rules actually prohibit us from accessing the local variables and parameters inside the function's body, so this separation of interface from implementation isn't just conceptual --- it's actually enforced by the compiler!

The prototype/body distinction in functions isn't enough to really separate interface from implementation in functions (or structs for that matter). Classes are aggregations of many data and function members. Some are part of the interface, i.e. are what you want the user of the class to worry about, some are just part of the implementation. The public/private mechanism is there to allow us to explicitly label members as part of the interface (public) or part of the implementation (private). In fact, in OOP we usually only allow member functions to be public, i.e. part of the interface. The reason is, the data members you choose are inherently an implementation decision. If you make them part of the interface, you will not be completely free to change the implementation of your class later ... you'd always have to keep those data members around. So, we arrive at the follwing:

A class's interface consists of its public methods (public member functions). In fact, the interface really only consists of the prototypes (aka signatures) of the class's public methods ... and maybe a bit of documentation.
The beauty of this is that if you keep this interface the same, you can completely change anything and everything about the implementation, and any program relying on the class should work just the same as always. Once again, the compiler actually enforces this separation of interface vs. implementation; it's not merely a conceptual distinction. If you try to access something marked private from outside a class ... the compiler won't allow it!

Encapsulation/information hiding
Another way to look at this public/private interface/imlementation thing is that the implementation details are actually "hidden" or "encapsulated". This means outside code can never be dependent on implementation details. It also means, in well-designed code, that outside code should never be able to screw up your class.

OOP and Design: the game of war
We talked about designing class and we centered our discussion around the bottom-up design of a program for playing the card game war. I won't recreate our class discussions, but we were working towards (and didn't finish) the following two classes:
Card.java
import java.util.*;

public class Card
{
  //-- PRIVATE MEMBERS ----------------------------------------//
  // A Card is represented by two indices: an index into the array
  // faces, and an index into the array suits.
  int faceIndex;
  int suitIndex;
  private static char[] faces = {'2','3','4','5','6','7','8','9','1','J','Q','K','A'};
  private static char[] suits = {'C','D','H','S'};
  private Card(int f, int s) { faceIndex = f; suitIndex = s; }

  //-- PUBLIC MEMBERS -----------------------------------------//
  public Card(String s)
  {
    faceIndex = 0; 
    while(faceIndex < 13 && faces[faceIndex] != s.charAt(0)) 
      ++faceIndex;
    suitIndex = 0;
    while(suitIndex < 4 && suits[suitIndex] != s.charAt(s.length() - 1))
      ++suitIndex;
  }

  public String toString()
  {
    char f = faces[faceIndex];
    char s = suits[suitIndex];
    return f == '1' ? "10"+s : " " + f + s; 
  }

  public Card nextHigherCard() 
  { 
    int s = (suitIndex+1)%4;
    int f = s == 0 ? faceIndex+1 : faceIndex;
    return f < 13 ? new Card(f,s) : null;
  }

  public int compareFace(Card c)
  {
    return faceIndex < c.faceIndex ? -1 : (faceIndex > c.faceIndex ? 1 : 0);
  }

  // This just tests the class by producing all cards from 2C up with lower
  // face value than JH.
  public static void main(String[] args)
  {
    Card c = new Card("JH");
    Card d = new Card("2C");
    while(d.compareFace(c) == -1)
    {
      System.out.println(d);
      d = d.nextHigherCard();
    }
  }
}

Pile.java
import java.util.*;
public class Pile
{
  //-- PRIVATE MEMBERS -----------------------//
  private Card [] A;
  private int n;
  private static Random generator = new Random();

  //-- PUBLIC MEMBERS ------------------------//
  public Pile() { A = new Card[52]; n = 0; }
  public void add(Card c) { A[n++] = c; }
  public Card pullTopCard() { --n; return A[n]; }
  public Card pullRandomCard() 
  {
    int i = generator.nextInt(n);
    Card c = A[i];
    --n;
    A[i] = A[n];
    A[n] = c;
    return c;
  }
  public static Pile makeDeck() 
  {
    Pile P = new Pile();
    P.A[0] = new Card("2C");
    while(++P.n < 52)
    P.A[P.n] = P.A[P.n-1].nextHigherCard(); 
    return P;
  }
  public void shuffle() 
  {
    int i = n;
    while(n > 0)
      pullRandomCard();
    n = i;
  }
  public String toString() 
  {
    String s = "";
    for(int i = 0; i < n; ++i)
      s = s + A[i] + " ";
    return s;
  } 

  // This just tests the basic Pile functionality.
  public static void main(String[] args)
  {
    // make deck and shuffle
    Pile P = makeDeck();
    System.out.println(P);
    P.shuffle();
    System.out.println(P);

    // deal two "hands"
    Pile H1 = new Pile();
    Pile H2 = new Pile();
    for(int i = 0; i < 5; ++i)
    {
      H1.add(P.pullTopCard());
      H2.add(P.pullTopCard());
    }
    System.out.println("Hand 1: " + H1);
    System.out.println("Hand 2: " + H2);
  }
}


Christopher W Brown
Last modified: Wed Aug 19 11:00:04 EDT 2009