Understanding Object Oriented Programming

The Problem

The problem to be solved is to output a value judgment about operating systems.

Simple Solution

 
public class PrintOS 
{
  public static void main(final String[] args) 
  {
         String osName = System.getProperty("os.name") ;
         if (osName.equals("SunOS") || osName.equals("Linux")) 
         {
                 System.out.println("This is a UNIX box and therefore good.") ;
         } 
         else if (osName.equals("Windows NT") || osName.equals("Windows XP")) 
         {
                 System.out.println("This is a Windows box and therefore bad.") ;
         } 
         else 
         {
                 System.out.println("This is not a box.") ;
         }
  }
}

Their claim: It works doesn't it what more do you want? Also I got mine implemented and working faster than any of the others, so there.

Evaluation

While this solves the problem, it would not be easy to modify in the future if the problem changes. In particular, if we need to add new operating systems, we need to extend the if structure. If we want to add additional functionality for each operating system, we would likely see this expand to nested if statements. This would get unwieldy over time. Thus, the programmer has solved the immediate problem, but made little progress on future evolution of the program.

Procedural Solution

 
public class PrintOS 
{
  private static String unixBox() 
  {
         return "This is a UNIX box and therefore good." ;
  }
  private static String windowsBox() 
         {
         return "This is a Windows box and therefore bad." ;
  }
  private static String defaultBox() 
  {
         return "This is not a box." ;
  }
  private static String getTheString(final String osName) 
  {
         if (osName.equals("SunOS") || osName.equals("Linux")) 
         {
                 return unixBox() ;
         } 
         else if (osName.equals("Windows NT") ||osName.equals("Windows XP")) 
         {
                 return windowsBox() ;
         } 
         else 
         {
                 return defaultBox() ;
         }
         }
  public static void main(final String[] args) 
  {
         System.out.println(getTheString(System.getProperty("os.name"))) ;
  }
}
 

Their claim: Java is a wonderful procedural programming language; it naturally supports top-down decomposition which is clearly the only way of analyzing and designing quality solutions to problems -- as exemplified by this example.

Evaluation

The procedural programmer has made some progress on the larger problem. If an operating system needs to be added, we extend the if statement in the getTheString function and add a new function for that OS. However, if the functionality of each OS needs to be extended, what we are likely to see is that the if statement will most likely be replicated elsewhere in the program each time we need to make the distinction between operating systems. Once that happens, whenever we add a new OS or change or add functionality we will need to find ALL of these if statements and update them compatibly. This is very error prone and results in entropy setting into such programs over time.

Naive Object Oriented Solution

This solution requires several classes in several files.

PrintOS.java
------------
public class PrintOS 
{
  public static void main(final String[] args) 
         {
         System.out.println(OSDiscriminator.getBoxSpecifier().getStatement()) ;
  }
}
 
OSDiscriminator.java
--------------------
public class OSDiscriminator // Factory Pattern
{
  private static BoxSpecifier theBoxSpecifier = null ;
  public static BoxSpecifier getBoxSpecifier()
  {
         if (theBoxSpecifier == null) 
         {
                 String osName = System.getProperty("os.name") ;
                 if (osName.equals("SunOS") || osName.equals("Linux")) 
                 {
                         theBoxSpecifier = new UNIXBox() ;
                 }
                 else if (osName.equals("Windows NT") || osName.equals("Windows 95")) 
                 {
                         theBoxSpecifier = new WindowsBox() ;
                 } 
                 else 
                 {
                         theBoxSpecifier = new DefaultBox () ;
                 }
         }
         return theBoxSpecifier ;
  }
}
 
BoxSpecifier.java
-----------------
public interface BoxSpecifier 
{
  String getStatement() ;
}
 
DefaultBox.java
---------------
public class DefaultBox implements BoxSpecifier 
{
  public String getStatement() 
  {
         return "This is not a box." ;
         }
}
 
UNIXBox.java
------------
public class UNIXBox implements BoxSpecifier 
{
  public String getStatement() 
  {
         return "This is a UNIX box and therefore good." ;
         }
}
 
WindowsBox.java
---------------
public class WindowsBox implements BoxSpecifier 
{
  public String getStatement() 
  {
         return "This is a Windows box and therefore bad." ;
  }
}

Evaluation

This programmer has made quite a lot more progress toward the goal of writing a maintainable program. In particular, if we need to add an OS, we extend the if statement as before, and write a new class for that OS. This is similar to what the procedural programmer had to do. However, if we need to add functionality for each OS, we only need to change the classes that deal with that OS. The if statement in OSDiscriminator is still a "logic bottleneck" but it is the only one in the program. This means that the location of change is easy to find (the classes that implement the functionality). Also, if we add functionality by changing the interface BoxSpecifier, then the compiler will tell us if some class fails to implement the new required functionality. We won't have to search the program for the locus of each change with no help from the tools.

Sophisticated Object Oriented

In the following, PrintOS.java and BoxSpecifier.java are unchanged from the above.

PrintOS.java
------------
public class PrintOS 
{
         public static void main(final String[] args) 
         {
         System.out.println(OSDiscriminator.getBoxSpecifier().getStatement()) ;
         }
}
 
OSDiscriminator.java
--------------------
public class OSDiscriminator // Factory Pattern
{
  private static java.util.HashMap storage = new java.util.HashMap() ;
  
  public static BoxSpecifier getBoxSpecifier()
  {
         BoxSpecifier value = (BoxSpecifier)storage.get(System.getProperty("os.name")) ;
         if (value == null)
                 return DefaultBox.value ;
         return value ;
  }
         public static void register(final String key, final BoxSpecifier value)
         {
         storage.put(key, value) ; // Should guard against null keys, actually. 
         }
         static
         {
         WindowsBox.register() ;
         UNIXBox.register() ;
         MacBox.register() ;
         }  
}
 
BoxSpecifier.java
-----------------
public interface BoxSpecifier 
{
         String getStatement() ;
}
 
DefaultBox.java
---------------
public class DefaultBox implements BoxSpecifier 
{
  public static final DefaultBox value = new DefaultBox () ;
  private DefaultBox() { }
  public String getStatement() 
  {
         return "This is not a box." ;
  }
}
 
UNIXBox.java
------------
public class UNIXBox implements BoxSpecifier 
{
  public static final UNIXBox value = new UNIXBox() ;
  private UNIXBox() { }
  public  String getStatement()
         {
         return "This is a UNIX box and therefore good." ;
  }
         public static final void register()
         {
         OSDiscriminator.register("SunOS", value) ;
         OSDiscriminator.register("Linux", value) ;
  }
}
 
WindowsBox.java
---------------
public class WindowsBox implements BoxSpecifier  
{
  public  static final WindowsBox value = new WindowsBox() ;
  private WindowsBox() { }
  public String getStatement() 
  {
         return "This is a Windows box and therefore bad." ;
         }
         public static final void register()
         {
         OSDiscriminator.register("Windows NT", value) ;
         OSDiscriminator.register("Windows 95", value) ;
  }
}
 
MacBox.java
----------
public class MacBox implements BoxSpecifier // Singleton Pattern
{
  public static final MacBox value = new MacBox() ;
  private MacBox() { }
  public  String getStatement()
         {
         return "This is a Macintosh box and therefore far superior." ;
  }
         public static final void register()
         {
         OSDiscriminator.register("Mac OS", value) ;
  }
}

Evaluation

Here we have turned the OS objects into singletons, so there can be only one such object in each of these classes. This may be desirable or not. If it is not, then the factory wouldn't return the objects in the hash table, but would return clones of them instead.

Here we have maintainable code. To add a new OS, like the Mac OS, we just add a new class and add its registration to the factory. To change the functionality we change the OS classes. To add new functionality, we either modify the OS classes, or extend them. Note that there is NO ad-hoc polymorphism here except the single test for null in the factory.