Link

Classes, methods and fields

Table of contents

  1. Refactor into classes and methods
    1. Observation
    2. Refactoring
  2. Is void a type?
  3. Fields (static no OOP)
    1. Observation
    2. Refactoring

Refactor into classes and methods

Consider the following example

package demo;

import java.time.LocalTime;
import java.util.Random;

public class App {
  public static void main( final String[] args ) {
   System.out.printf( "[%tH:%<tM:%<tS] Game started%n", LocalTime.now() );
   System.out.printf( "[%tH:%<tM:%<tS] Please roll the 🎲%n", LocalTime.now() );

   final Random r = new Random();
   final int a = r.nextInt( 6 ) + 1;
   final int b = r.nextInt( 6 ) + 1;

   System.out.printf( "[%tH:%<tM:%<tS] You rolled %d and %d%n", LocalTime.now(), a, b );
  }
}

Output

[12:34:56] Game started
[12:34:56] Please roll the 🎲
[12:34:56] You rolled 2 and 1

Observation

  1. The main() method is cluttered and cannot easily say what’s happening

  2. Cannot understand what the following is doing by simply reading the code

    final int a = r.nextInt( 6 ) + 1;
    

    We can deduct that this is related to rolling of dice based on the log messages preceding and following this statement.

  3. All output messages have the same format:

    System.out.printf( "[%tH:%<tM:%<tS] message%n", LocalTime.now() );
    
  4. It is not easy to test the above code despite its simplicity

Refactoring

  1. Move the dice logic to a separate class

    Create a file called Dice.java

    package demo;
    
    import java.util.Random;
    
    public class Dice {
    
     public static int roll() {
       final Random r = new Random();
       return r.nextInt( 6 ) + 1;
     }
    }
    
    ⓘ NoteNote that a class which is declared `public` should be in a file of the same name**, otherwise it will not compile. The public class `Dice` must be in a file with the same name, Dice.java.
  2. Use the new method roll() defined in the Dice class

    package demo;
    
    import java.time.LocalTime;
    
    public class App {
     public static void main( final String[] args ) {
       System.out.printf( "[%tH:%<tM:%<tS] Game started%n", LocalTime.now() );
       System.out.printf( "[%tH:%<tM:%<tS] Please roll the 🎲%n", LocalTime.now() );
    
       final int a = Dice.roll();
       final int b = Dice.roll();
    
       System.out.printf( "[%tH:%<tM:%<tS] You rolled %d and %d%n", LocalTime.now(), a, b );
     }
    }
    
  3. Move the messaging logic to a separate class

    Create a file called Display.java

    package demo;
    
    import java.time.LocalTime;
    
    public class Display {
      public static void print( final String message ) {
        System.out.printf( "[%tH:%<tM:%<tS] %s%n", LocalTime.now(), message );
      }
    }
    
  4. Use the new method print() defined in the Display class

    package demo;
    
    public class App {
      public static void main( final String[] args ) {
        Display.print( "Game started" );
        Display.print( "Please roll the 🎲" );
    
        final int a = Dice.roll();
        final int b = Dice.roll();
    
        /* We still had to format the string */
        Display.print( String.format( "You rolled %d and %d", a, b ) );
      }
    }
    
  5. Add formatting support to the Display class

      public static void printf( final String pattern, final Object... parameters ) {
        print( String.format( pattern, parameters ) );
      }
    

    Full example

    package demo;
    
    import java.time.LocalTime;
    
    public class Display {
      public static void printf( final String pattern, final Object... parameters ) {
        print( String.format( pattern, parameters ) );
      }
    
      public static void print( final String message ) {
        System.out.printf( "[%tH:%<tM:%<tS] %s%n", LocalTime.now(), message );
      }
    }
    
  6. Use the new printf() method

    package demo;
    
    public class App {
      public static void main( final String[] args ) {
        Display.print( "Game started" );
        Display.print( "Please roll the 🎲" );
    
        final int a = Dice.roll();
        final int b = Dice.roll();
    
        Display.printf( "You rolled %d and %d", a, b );
      }
    }
    
  7. How does refactoring simplify testing?

    Not much. The code is simpler to ready, but still very hard to test.

    One key takeaway here is that using static methods, bound our main() method to the Dice.roll() and the Display.print() methods and this makes our method hard to test. There are tools we can use that will allow us to test our code, but issue here is our design.

    ⓘ NoteAlways tackle the root problem and do not throw more complexity at it.

Is void a type?

The roll() method in the Dice class returns int

public static int roll() {
  final Random r = new Random();
  return r.nextInt( 6 ) + 1;
}

The print() method in the Display returns void.

public static void print( final String message ) {
  System.out.printf( "[%tH:%<tM:%<tS] %s%n", LocalTime.now(), message );
}

Is void a type? No, the keyword void is not a type.

Consider the following example.

⚠ The following example does not compile!!
package demo;

public class App {

  public static void main( final String[] args ) {
   int a;
   /* ⚠️ Cannot create a variable of type void!! */
   void b;
  }
}

While int is a type and we can create a variable of type int, void is not a type. The keyword void is used by methods to indicate that the method returns nothing.

This quite unique to Java as other languages always return a type. The decision of having void as a non-type caused some complications in the newer versions of Java, such as lambda.

Fields (static no OOP)

Consider the following program

package demo;

import java.util.Random;

public class Dice {

  public static int roll() {
   final Random r = new Random();
   return r.nextInt( 6 ) + 1;
  }
}

Observation

  1. Every time the roll() method is invoked, a new instance of Random is created.

    Do we need to create a new instance, every time we invoke the roll() method?

Refactoring

  1. The random variable can be moved outside the method and make it a class level variable

    package demo;
    
    import java.util.Random;
    
    public class Dice {
    
      public static int roll() {
        return RANDOM_GENERATOR.nextInt( 6 ) + 1;
      }
    
      public static final Random RANDOM_GENERATOR = new Random();
    }
    

    Note that we now have a longer and more meaningful name, RANDOM_GENERATOR. The bigger the variable scope, the more thought needs to be put into the variable’s name.