think like an object
last | | contents | | next


If you can't say something, you can't say it wrong.
Fred Brooks

We've now seen most of the bits the Java interpreter understands all by itself, but there is still much to learn. It's not enough to learn to speak a language; we must also learn to speak it well.
 
The Magic of Names

Consider the following problem, sketched in the first chapter, of giving a collection of person classes a getName() method. As soon as you start thinking about how to implement this seemingly simple behaviour you run into problems having nothing to do with the original problem it is a tiny part of. The distance between what the computer understands natively and what you need it to understand to solve your problem is immense.

At first blush, you might think that implementing such a method is a simple matter of returning a String, in a String variable called, say, name.


public String getName()
	{
	return this.name;
	}

Simple enough, it seems; but wrong, because a person's name isn't just a String, nor need it be unique, nor need it be always publically available to anyone. A name is something that has meaning in the human world. Not anyone can request it in any circumstance. Not anyone can change it anytime they want to, either. It can be changed, of course, but usually only under exceptional circumstances, and with all sorts of provisos on the change. Further, even when changed, the object it belongs too frequently remembers, and will answer to, the original name. It is often registered somewhere, usually in multiple places, and must be reconciled everywhere it is stored or used. It has parts, usually at least a first name and a last name, and often occurs in multiple variant forms---nicknames, pet names, work names---all referring to the same person given the right circumstance. Multiple persons can share their entire names, yet that may have no meaning, while multiple persons sharing only their last name, but having the same address may well have meaning. None of these properties are true for mere Strings.

When you need to use a person's name in a Java program what you really want is not a String stored in a String variable called name, you want an instance of class PersonName. So instead of something like:


public String getName()
you really want:

public PersonName getName()
If you had such a thing there are vast masses of errors that you simply can't make. For example, you couldn't simply overwrite someone's name with another name, as is trivial to do when name is just a String, and you write something like the following method for persons:

public void setName(String name)
	{
	this.name = name;
	}
To change any person's name you'd have to do something more involved like:

try
	{
	somePerson.setName(newPersonName, authorization, this);
	}
catch (PersonNameChangeException exception)
	{
	//stuff to do if the person refused to change its name...
	}
Class Person would store an instance of class PersonName, which it would delegate all name change requests to. Class PersonName would know all about people's names and how to handle anything to do with them, including registering them and changing them, and the various authorizations you need to do so. It might inherit behaviour about names in general from another class, say, Name, which would know general things about any kind of name, not just person names. Class Person would be responsible for acquiesing (or not) to requests for that person's name---depending on who's asking and when and why and so forth---it might give its name, or it might throw an exception.

If you want some special behaviour for your particular people's names, you subclass Person and override the default protections on names, but if you don't need anything special, you just let Person, PersonName, and Name do their jobs, without having to think at all about how names are created, managed, controlled, and used.

Consider a payroll application with an Employee class hierarchy and some method like


public calculatePay(String name, double salary, int numberOfSickDays)
	{...
an easy error to make when writing such a method is to accidentally mangle numberOfSickDays. Just as with the personName implemented as a String example, numberOfSickDays is not really an int, the payroll system's programmers are merely pretending it's an int and carrying around all the real semantics of sick days in their heads. The number of sick days cannot be larger than the number of days in the pay period, for example, nor can it be negative, nor can it change during the program's run---unless it was inaccurate to begin with. more importantly, having to remember all that and watch for misuse of these trivial details of just this one small variable is tiring and stressful, which increases the chance that something else will slip through.

but what is the alternative? surely we can't take the time to make a special WorkDay object, then a SickDay subclass, and so on, just to protect ourselves from misusing one variable in our giant payroll application?

But we are a long way from there. Right now we're still at the dawn of computing, and programmers must create and keep track of all of that detail having to do with name management even when it's just a tiny part of whatever giant application it is that they're presently writing.

The central idea behind elegant programming is to protect us programmers from ourselves. Face it: we're lazy, shiftless, and forgetful. To help us not make quite as many silly mistakes as we usually do, the Java interpreter lets us make each object responsible for its own state and behavior. Unfortunately, it doesn't force us to do so.

Many problems result when we stray from the straight-and-narrow of having each object be the sole controller of its own state and let other objects arbitrarily change that state.

Elegant programming becomes much simpler if we always put ourselves in the object's place. As objects we have variables and methods (state and behavior). Other objects exist. They work with us to do complex things. To do so, they will ask us to do things and we will ask them to do things.

Thinking like an object, the simplest step towards the goal of utter encapsulation is to keep all variables and methods private. Of course, that's usually too extreme a solution since then no other object can ask such objects to do anything, so they're effectively outside the system they're supposedly a part of. There can be a role for such an object in some Java programs though, so we won't completely rule out the possibility.

Short of that extremity, we can make some of our methods public as the need arises, but we should always make our variables private. Non-private variables have no way to defend themselves against arbitrary changes by other objects. Once we make a variable public it is helpless against the barbarian marauders.
 
Encapsulating Variables

What's the point of defining a variable private, if we are going to also have public set and get methods for that variable? Making the variable public and getting rid of the public methods is functionally equivalent, and shorter and simpler. Besides, public get and set methods give no extra protection to the variable than simply making it public in the first place, so why bother?

In an ideal world, it wouldn't be necessary. In this world, however, stupidity is everywhere; it's the universe's most abundant element, even more so than hydrogen. Making all variables private helps us to protect ourselves, which makes our program more robust and more flexible. Here's why:

* If tomorrow we decide to take away the variable entirely, perhaps replacing it with some other variable or a computation, or if we decide to change its type, nobody's code has to change but our own. Changes are localized to the nearest encapsulation wall (the boundaries of our class---or, in the best case, the enclosing method). This locality makes it easier to find and localize changes and to track down bugs.
 
* By directly referencing our public variables, other classes are tied to us. Any change in them may require a change in us, and vice versa. The more coupled our classes are, the more complex our program is, since anything may influence almost anything else.
 
* Once we've protected our variables with a shell of public getters and setters we can protect them even more. For example, by giving a variable public accessors and private setters, the variable becomes read-only to other objects. Anyone may read the value of the variable, but only objects of the class itself may alter it. Similarly, we can make write-only variables, which can be written to but not read by other objects; although such a variable seems pretty silly, there might be a use for it.
 
* With such a shell we can add code to count the number of times the variable is accessed or changed, and otherwise control how and when it is used---and even who can use it.

Protecting our class's variables with a shell of public getters and setters gives our classes control of their own destiny. The shell gives us power, safety, and control. A shell of getters and setters around a private variable gives us freedom and takes away the possibility of damage caused by ignorant users. Encapsulation simultaneously protects us and it protects them. None of that is possible with a naked public variable.

Of course, only a stupid programmer will misuse our public variables, right? Unfortunately, all programmers are stupid programmers. We all make mistakes---it's only a question of how often we do so. In the face of the immensity and insane complexity of a large program we're all idiots. The less that other programmers have to think about how to use our code the more likely they are to use it correctly.

The first rule of being a good programmer---not someone who can grind out a lot of code quickly, but someone who creates beauteous code that can live on for a long time---is to always assume that all programmers are complete idiots. Once we know that we're inevitably going to forget stuff, make mistakes, and generally goof at the stupidest, most inopportune time, we will know enough to make it easy on ourselves by programming as defensively as possible right from the start.

Finally, no matter how we protect ourselves some nitwit somewhere can always find a way to undo the protection and mess things up. So why bother with any kind of protection at all? By that reasoning, though, we should give all newborns loaded handguns with the safety sawn off, because if we didn't then they could easily have accidentally crawled up to the gun cabinet, picked the lock, gotten out the gun, loaded it, flipped the safety off, turned it on themselves, and fired. We protect our users by protecting ourselves.
 
Encapsulating Constants

We can use interfaces to keep track of common constants in a group of classes. For example, instead of having to set constants using code like:


public class SomeClass
   {
   //weekday constants
   public final static int SUNDAY = 0;
   public final static int MONDAY = 1;
   //and so on
   }

in every class we need to refer to them, we can just declare, say, a Weekday interface, like so:

public interface Weekday
   {
   //weekday constants
   public final static int SUNDAY = 0;
   public final static int MONDAY = 1;
   //and so on
   }

Then to use those constants in, say, a daytimer class or a calendar class, we can just have whatever classes that need to know about weekdays implement the Weekday interface. That way they can all refer to Weekday.SUNDAY and be done with it. Further, we don't have to go through all our code and change the settings if for some reason we want to alter the values kept in the Weekday constants.

This is a good, but not great, solution. If we implement the Weekday interface in any class we can freely use Weekday.SUNDAY, but

* We always have to remember that it's really just an int when assigning to something or testing it against something.
* We could, if we wanted, assign any old int to an int variable intended only to hold Weekday constants.
The Java interpreter doesn't help us in the first case and wouldn't complain in the second case because there's no connection between those variables and the values we can put in them.

This leaves a hole for bugs to skitter in. The next programmer to modify our code, for example, might do something stupid like declaring a yesterday variable and calculating it by subtracting one from a today variable then forgetting to check that today isn't Weekday.SUNDAY.

Such bugs can sneak in because the Weekday interface is not a firm contract between our classes. It's not being strictly enforced by the Java interpreter.

Instead, we need some way to tell the interpreter all about the special properties that any Weekdays must have and let it worry about checking that we always adhere to those properties. In other words, we need to make Weekday into a strict type.
 
Using final for Classes and Methods

Recall that every variable can be final, with the meaning that its initial value can't ever be changed. This is true not just for global variables, but also local variables inside a method or subblock of a method, and parameter variables to a method. It doesn't matter if the variable is local or global; all variables can be made final. It's good style to make as many variables as possible final. That way, we know they can never change once they have their initial values. It's a style crime to not make a variable final if it really should be constant.

We can do something similar with classes and methods. A final method can't be overridden in a subclass. A final class can't be extended by a subclass.

A static method is implicitly final since it can't be overridden in a subclass (only the secret object of its class can execute it). An abstract method, however, is never final since then it couldn't be overridden in a subclass---which means that no subclass could ever create objects since it would always have at least one abstract method.
 
Using private Constructors

Sometimes we need a class to strictly control how many, or what kinds of objects, it can be asked to create. This is impossible without some special tricks, since as far as we know right now objects of any class with access to the given class can create any number of objects of that class.


public class OnlyOne
   {
   private static OnlyOne onlyOne = new OnlyOne();

   private OnlyOne()
      {
      /*
      Initialize the single OnlyOne object.
      */

      //some code
      }

   public static OnlyOne getOnlyOne()
      {
      return onlyOne;
      }
   }

import OnlyOne;

class testOnlyOne
   {
   public static void main(String[] parameters)
      {
      OnlyOne one = OnlyOne.getOnlyOne();
      OnlyOne two = OnlyOne.getOnlyOne();
      if (one == two)
         System.out.prinltn("the two objects are identical");
      }
   }

This class intializes itself. It makes only one object ever and only the secret object associated with the class will ever have direct access to it. Other objects can use the getOnlyOne() to get a reference to that private object.
 
Encapsulating Weekdays

Here's a solution to the problem of encapsulating constants that completely eliminates all holes:


public final class Weekday //disallow subclassing
   {
   public final static Weekday SUNDAY = new Weekday();
   public final static Weekday MONDAY = new Weekday();
   //and so on

   private Weekday() //disallow outside creation
      {}

   public String toString()
      {
      if (this == SUNDAY)
         return "SUNDAY";
      else
      if (this == MONDAY)
         return "MONDAY";
      //and so on
      }
   }

Making class Weekday final makes it unsubclassable. That prevents anyone from subclassing it, monkeying with the subclass's properties, then passing an object of the subclass as an object of class Weekday and thereby messing up all our carefully laid checks.

Declaring the Weekday constructor prevents the interpreter from secretly inserting a zero-parameter (and public) constructor. Then making the constructor private ensures that only class Weekday can create objects of class Weekday.

So for the cost of making seven objects, instead of seven ints, the Java interpreter does all our type-checking for us.

The beauty of this scheme only becomes really clear when we see that we can also declare variables to be of type Weekday and have full type checking, again by the interpreter.

For example, in any class that imports class Weekday we can say,


private Weekday weekday = Weekday.SUNDAY;

and use that Weekday variable just as we would any other variable. The special thing we know about this variable is that it cannot assume more than seven different values. What those values are is completely irrelevant; all we need to know is their names. Bugs don't have a chance to invade this code---it's completely bulletproof.

One further advantage: since it's now a class and not an interface, we can add methods to class Weekday to do whatever Weekday things that might need doing (for example, calculating what day yesterday is) and leave it all inside class Weekday.

There is no longer any possibility of assigning an incorrect value to a Weekday variable, nor is there ever any possibility of incorrectly testing against a Weekday constant. Class Weekday is now fully encapsulated and safe as houses.
 
Nested Classes

Classes can be nameless. First though we have to understand nested classes. All this time the only two kinds of things we've ever seen in a class are methods and variables. we can also have classes inside classes. These are nested classes.
 
Encapsulating Classes

The Java interpreter won't let us declare a class private (or protected), but we can still make a class behave almost as if it were private within a package.


package privacy;

import testit.Test1;
import Test2;

final class PuppetMaster
   {
   /*
   Implement a class that no one inside the package
   can invoke in any way, except to execute its main() method,
   and no one outside the package can even see.
   This class can invoke any publically accessible class
   inside or outside its package.
   */

   private PuppetMaster()
      {}

   private void invoke()
      {
      new Test1().invoke();
      new Test2().invoke();
      }

   public static void main(String[] parameters)
      {
      new PuppetMaster().invoke();
      }
   }


package testit;

import Test2;

public class Test1
   {
   public void invoke()
      {
      System.out.println("hello");
      new Test1().invoke();
      }
   }


import testit.Test1;

public class Test2
   {
   public void invoke()
      {
      System.out.println("goodbye");
      new Test2().invoke();
      }
   }

PuppetMaster is a secret class in the privacy package, Test1 is in some generic testit package, and Test2 is in the unnamed default package. PuppetMaster can request execution of any public method of any class outside its package, and any non-private method of any class inside its package. Objects of class Test1 can address objects of class Test2, and objects of class Test2 can address objects of Test1, but no one can request execution of PuppetMaster code (except for main()). No one outside of package privacy even knows PuppetMaster exists.

Further, even if there were other classes in package privacy, none of them could request execution of any method of PuppetMaster except its main() method---which would then go about doing its stuff to all other publically accessible classes.

Finally, no object can create a new PuppetMaster directly, nor can we override its effects by subclassing it. This is as private as the Java interpreter lets a class get.
 
All About Contracts

Encapsulating our code is far more than just making all variables private and adding public getters and setters to control their state; it's about taking control of all manipulations of our code's state. It's about freedom.

Suppose, for example, that we want to give some objects of different classes a unique identifier. Say we have to put identifiers on some car parts of different types in a car factory. Each object (car part) must have its own identifier and there may be many classes (kinds) of objects.
 
Solution I

One obvious solution is to give each class its own range of int identifiers:


class SomeClass
   {
   //assign an initial identifier value
   //for the range of identifers
   //to be used by objects of this class
   private static int identifierNumber = xxxx;

   //declare my identifier
   private int identifier;

   public SomeClass()
      {
      /*
      Set my identifier and do other constructor stuff.
      */

      identifier = identifierNumber++;

      //other constructor code...
      }

   public int getIdentifier()
      {
      /*
      Report my identifier.
      */

      return identifier;
      }

   public void setIdentifier(int identifier)
      {
      /*
      Let anyone reset my identifier.
      */

      this.identifier = identifier;
      }

   //other code to implement SomeClass's behavior...
   }

This solution might look quite reasonable, but it is chock full of style crime.

First, SomeClass objects have no real control over their identifiers, even though identifer is private. The setIdentifier() method is public---so any object could alter a SomeClass object's identifier at any time. Simply making all variables private and adding getters and setters does not encapsulate a class.

Second, SomeClass objects have no real control over the uniqueness of their identifiers, since we might create more objects than there are numbers in that class' (implicit) range. Making the ranges number in millions only postpones the problem---as the Year 2000 problem illustrates today. More than that, though, it's a sign of a deeper problem with the code: identifiers have nothing in particular to do with int values.

Third, because each class has its own range of identifiers hardwired in, we must have predecided exactly how many identifiers there will be for each type. Consequently, there is no flexibility at all in this code.

In sum, this code commits several style crimes because to work properly it relies completely on the programmer to always do the right thing. Whoever has to write or modify all the client classes using identifiers have to know everything about all identifiers---and remember to make all the right changes in all the right places. Which means of course that someone will forget something one day and catch a nasty bug. Never treat programmers like the machines they use.
 
Solution II

A slightly less nasty version creates a separate Identifier class to hold identifiers and gets rid of the public setIdentifier() method entirely:


class SomeClass
   {
   //declare my identifer
   private int identifier;

   public SomeClass()
      {
      /*
      Set my identifier.
      */

      this.identifier = Identifier.currentIdentifier++;
      }

   public int getIdentifier()
      {
      /*
      Report my identifier.
      */

      return identifier;
      }
   }

class Identifier
   {
   /*
   Hold the current identifier value in a class variable.
   */

   public static int currentIdentifier = 0;
   }

This is a bit better, but it's still full of style crime.

First, the programmer working on SomeClass has to remember to increment the currentIdentifier variable. Multiply that effort by the number of classes that have to have identifiers and we have a problem. Worse, if it isn't done, or is done improperly, it could lead to subtle bugs without causing any fatal errors. This is a variant of the previous issue: don't treat programmers like machines.

Second, the code is not thread-safe. If multiple objects are each running in their own thread and each want to get an identifier, they will each try to increment the same class variable (in class Identifier) and might step on each other's toes. To solve that problem we could synchronize on class Identifier, but that's not a good solution. It's a band-aid because it doesn't address the deeper problem, which is that currentIdentifier is being treated as a classwide-global variable.

Third, we're in trouble if we ever change our assumptions about identifiers to reserve ranges of identifiers, reuse identifiers of destroyed objects, keep track of how many identifiers are presently in use, ensure concurrent access to identifiers, add check digits to identifiers to ensure validity, or, indeed, make any change whatsoever to identifiers. To make any such change may mean changing every single occurrence of the variables identifier and currentIdentifier everywhere in the code.

A telltale sign that this is a bad solution is that the new Identifer class is nothing more than a glorified data storage device---it has no behavior. A good way to tell whether something is a true object is to see whether it has state (variables) and behavior (methods to operate on those variables). If it's missing one or the other, it likely isn't an object and should be folded into whichever class that uses it. If there are many such classes then the design is broken and needs to be rethought so that the bad class is given state and behavior.
 
Solution III

A more sophisticated solution encapsulates identifiers still further by removing all knowledge of how identifiers are generated from the client classes:


class SomeClass
   {
   //declare my identifier
   private int identifier;

   public SomeClass()
      {
      /*
      Set my identifer.
      */

      identifier = Identifier.getNewIdentifier();
      }

   public int getIdentifier()
      {
      /*
      Report my identifier.
      */

      return identifier;
      }
   }

class Identifier
   {
   /*
   Hold the current identifier value in a class variable.
   */

   private static int currentIdentifier = 0;

   public static int getNewIdentifier()
      {
      /*
      Generate and return a new identifier.
      */

      return currentIdentifier++;
      }
   }

This is the kind of code commonly displayed in programming languages books as being "well-encapsulated". It's of course much better than the previous stabs in the dark but it still leaves unnecessary coupling between class Identifier and its client classes. For example, all the client classes still have to know that identifiers are integers. So if we ever decide to change that type, then all the clients will have to change their declarations of identifiers. The Identifier encapsulation is still incomplete.
 
Solution IV

An even better solution completely encapsulates all identifier assumptions into class Identifier:


class SomeClass
   {
   //declare my identifier
   private Identifier identifier;

   public SomeClass()
      {
      /*
      Set my identifier.
      */

      identifier = Identifier.getNewIdentifier();
      }

   public Identifier getIdentifier()
      {
      /*
      Report my identifier.
      */

      return Identifier;
      }
   }

class Identifier
   {
   /*
   Encapsulate the idea of an identifier
   by creating, returning, managing, and testing identifiers
   for arbitrary client classes.
   */

   private static int currentIdentifier = 0;
   private int identifier;

   private Identifier()
      {
      /*
      Set the secret identifier
      for this particular Identifier object.

      Disallow outside creation
      to keep full control of Identifier creation.
      */

      identifier = currentIdentifier;
      }

   public static synchronized Identifier getNewIdentifier()
      {
      /*
      Thread-safely generate and return a new Identifier.
      */

      currentIdentifier++;
      return new Identifier();
      }

   public String toString()
      {
      /*
      Return a String representation of an Identifier.
      */

      return "" + identifier;
      }

   public static boolean equals(Identifier identifier1,
      Identifier identifier2)
      {
      /*
      Test whether two Identifiers are equal.
      */

      return (identifier1 == identifier2);
      }

   //other identifier code...
   }

Each client class now gets an Identifier object and that's all it knows, or has to know. Hidden inside that object is the identifier, but that's not for the client classes to know about, or to care about. Class Identifier takes care of all the identifier management. It is now fully encapsulated.

The client classes' complete ignorance of what's inside an Identifier object is, paradoxically, good for everyone. It's easy to see, for example, how to change class Identifier to use Strings as identifiers, say, rather than ints, and not have the change affect any of the client classes:


class Identifier
   {
   /*
   Encapsulate the idea of an identifier
   by creating, returning, managing, and testing identifiers
   for arbitrary client classes.
   */

   private static String currentIdentifier = "";
   private String identifier;

   private Identifier()
      {
      /*
      Set the secret identifier
      for this particular Identifier object.

      Disallow outside creation
      to keep full control of Identifier creation.
      */

      identifier = currentIdentifier;
      }

   public static synchronized Identifier getNewIdentifier()
      {
      /*
      Thread-safely generate and return a new Identifier.
      */

      currentIdentifier += "1";
      return new Identifier();
      }

   public String toString()
      {
      /*
      Return a String representation of an Identifier.
      */

      return identifier;
      }

   public static boolean equals(Identifier identifier1,
      Identifier identifier2)
      {
      /*
      Test whether two Identifiers are equal.
      */

      return (identifier1.equals(identifier2));
      }

   //other identifier code...
   }


 
Conclusion

Now we can see what's at the heart of good style: class Identifier establishes a contract with the classes that use it (its clients). Class Identifier promises to do certain things (its public methods collectively form its API) and that's all its client classes have to know. The less they know about the implementation of class Identifier the less they change if class Identifier changes.

To establish contracts between all the involved classes we must face the issue of exactly what an identifier is when we're creating class Identifier. That constraint helps us produce the most insight and so the best code for class Identifier and its clients.

Because class Identifier has been encapsulated, then once its contract has been decided, a programmer on the other side of the world can implement it in complete isolation from whoever is implementing its client classes.

The core identifier properties we've discovered are:

* an object's identifier is unique,
* an object's identifier is fixed,
* two object identifiers can be tested for equality,
* there are an unlimited number of identifiers, and
* an object can publically display its identifier.

Of course, a good programmer's job is never done. Suppose we later decide that the contract for class Identifier should stipulate that an object can address another object given only its Identifier. To make that change could require us to change all the requests to execute getNewIdentifier() to add a reference to the requesting object, which might then be saved in a private table internal to class Identifier.

Because it breaks the contract, that new condition requires us to change potentially a lot of method requests. Even then, though, all that would really have to change is the client's requests to execute getNewIdentifier (or we might use reflection, which we won't learn about til later). Of course, the best solution is to think of this condition when we're designing class Identifier's contract in the first place.

Further, if we have to create thousands of objects Identifier creation might be a bottleneck since, at bottom, it presently depends on exactly one class variable. It might be better to distribute the work by creating multiple Identifier creator objects, each with their own variables and some way to probabilistically guarantee identifier uniqueness (generating large random numbers should do the trick).

Finally, there is nothing in the current contract that prevents a client object from requesting lots of identifiers and producing different ones when asked for its identifier. (Yes, that would probably take malice on the part of the client class programmer, but it might happen accidentally somehow.) Ideally, each client object should only ever get one Identifier and that protocol should be enforced exclusively by the Identifier class. But this is stepping into pretty rarefied areas that most programmers don't consider at all, so it's probably time to stop and sum up.

Elegant programming protects us by letting us establish firm contracts between different objects. Establishing firm contracts is what leads to clean, robust, reusable, and repurposable code. Learning the mere statements of the Java interpreter understands is useless if this lesson is lost.
 
Fortune Cookies

Two groups of programmers wish to come to some agreement about how to communicate with each other by passing objects. For example, suppose that an interface wants to notify a user model about interface events. The question at hand is this: should the modelers require the interfacers to produce one class, say InterfaceEvent, which takes a string (or, some predefined magic numbers) in its constructor to specify what kind of event it was ("Entity Opened", "Entity Selected", "Entity Moved", and so on) or should they have one superclass, InterfaceEvent, and some subclasses (EntityOpenedInterfaceEvent, EntitySelectedInterfaceEvent, EntityMovedInterfaceEvent, and so forth)?

The first solution can only lead to wailing and gnashing of teeth in future. Here's why:

If we go the string route (or magic number route, which is really the same thing) then all the code that is concerned with InterfaceEvents forever and always is going to need the equivalent of a series of if-else statements to disambiguate the cases. The class of the object, InterfaceEvent, is not particularly useful to us. It is simply a carrier for the true nugget of information---the string or magic number inside it, put there during its construction. It's a fortune cookie.

Such code is notoriously hard to extend since any time we want to make slight changes in InterfaceEvents (for example to add a new one) we have to go through all the code (both to produce InterfaceEvents and to consume InterfaceEvents) and modify it. And complex if-else statements, in particular, are notoriously easy to break.

It's a maintenance nightmare. It's the typical dreadful code that hackers around the world have to deal with day after day and is one of the leading causes of that notorious malady: Code Bugginess.

Instead, suppose we defined some subclasses of InterfaceEvent, say EntityOpenedInterfaceEvent, EntitySelectedInterfaceEvent, EntityMovedInterfaceEvent, and so on. Let's give InterfaceEvent a method, say, report(). In each of the subclasses we override report() and do something specific for that subclass.

To use these objects we simply pass in the object as its superclass type, InterfaceEvent, and ask it to report() itself. The objects then do the entire job for us that previously we had to use lots of buggy if-else statements for.

What are the gains?

* We no longer have to care what the subtype of the InterfaceEvent object is (nor do we even have to care if it has more than one subtype).
 
* If we decide tomorrow to add a new InterfaceEvent (say, ScreenOnFireInterfaceEvent) all we have to do is create a new subclass of InterfaceEvent and write its report() method. We don't have to change the handler code at all.
 
* The interpreter takes care of matching types to code for us, so there can never be a mistake in future.

Here is the secret of elegant programming: instead of using objects to simply carry state and putting all the smarts in handler code (which then has to change if the object's state changes) as in the fortune-cookie solution, the object itself also carries its own behavior (in this case, the report() method) that gives it enough intelligence for it itself to figure out how to handle itself depending on its state. Adding new objects, or modifying old objects, is then a snap.

Using objects only to carry state is a lose.

Using the instanceof operator here doesn't make a whole lotta sense either since that's just a slightly more sophisticated version of the same old if-else statement solution sketched above. It has the same maintenance problems. The isInstance() method, a dynamic version of the instanceof operator, leads to similar problems with mindless use. Code that uses if-else statements and instanceof (or isInstanceOf()) in place of smart objects may look like Java code, but it's not really. We should never use either of them if we are really just checking for every single subtype of some type.
 
Put the Intelligence Where It Belongs

The issue of fortune cookies is much larger than the above example, it's a question of where to put the intelligence in a program. Traditionally programmers never worried too much about this: they put the intelligence wherever they felt it was needed. This is sometimes okay, sometimes not. Ideally, though, we should push the intelligence as far down as possible, all the way down to methods (or in fact variables themselves, by surrounding them with methods we can add intelligence to). leaving things naked at any level leaves us exposed.
 
Programming By Contract

In general, we want to enforce contracts on how various things can be used. java takes us part-way to this goal, but falls far short of what an ideal language would let us do. Simple exmple: method assertions.

More complex examples: cross-method dependence. Simple example of that: accessor methods for a single variable.
 
Encapsulation

Encapsulation is only a special case of this general idea.

Example: making brightness into a type responsible for making sure that it is never assigned an out-of-range value. all those stupid if tests cluttering up the Lamp code simply to make sure of that is a style crime. we haven't pushed the intelligence far enough down yet.

there is no limit checking on brightness. Any lamp can alter a lamp's brightness without first turning it on, or can decrease the lamp's brightness to zero yet not turn it off. Really, brightness should not be an int at all since not every int value can be put into it; it has a much smaller range. It should be a value of a completely new type. lamps shouldn't have code to check this; it's not a lamp's business.

Example: The println() method that takes multiple types and does the right thing with them.


last | | contents | | next