Strategy Pattern, 5 examples from Java core

Strategy pattern is one of the well known behavioral patterns, yet people (at Java interviews in my city) find it difficult to name some example from Java SE although most of them know how to define the pattern and most are able to give some random examples. Strategy pattern is used all over the place in Java and I will show you five examples in this blog post. Leave a comment below with other examples you can find.

java.util.Comparator

This is the classic example: sorting lists using different compare strategies. In the code below, the strategy abstraction is Comparator, the concrete strategy implementations are LexicographicComparator and ByLengthComparator. Sorting is not the only method in Collections that uses the Comparator strategy, there are others like: binarySearch,  min, max. The highlighted lines are the places where the different strategies are applied.

class LexicographicComparator implements Comparator<String> {
  public int compare(String o1, String o2) {
    return o1.compareTo(o2);
  }
}

class ByLengthComparator implements Comparator<String> {
  public int compare(String o1, String o2) {
    return Integer.compare(o1.length(), o2.length());
  }
}

public class ComparatorExample {
  public static void main(String[] args) {
    List<String> fruits = Arrays.asList(
        "watermelon",
        "apple",
        "pear");

    Collections.sort(fruits, new LexicographicComparator());
    // will print [apple, pear, watermelon]
    System.out.println(fruits);

    Collections.sort(fruits, new ByLengthComparator());
    // will print [pear, apple, watermelon]
    System.out.println(fruits);
  }
}

java.util.logging.Formatter

Another example of how strategy pattern is used in Java is log formatters. Formatters change the way the logging files look like. The code below creates a logger object and configures it with two console handlers, each of them configured with a different log formatter: the first one uses an XMLFormatter, the second one a SimpleFormatter. These two classes are concrete implementations of the abstract strategy Formatter.

public class FormatterExample {
  public static void main(String[] args) {
    Logger logger1 = Logger.getLogger("logger1");
    logger1.setUseParentHandlers(false);

    ConsoleHandler handler1 = new ConsoleHandler();
    handler1.setFormatter(new XMLFormatter());

    ConsoleHandler handler2 = new ConsoleHandler();
    handler2.setFormatter(new SimpleFormatter());

    logger1.addHandler(handler1);
    logger1.addHandler(handler2);

    logger1.info("Info message");
    logger1.warning("A warn message");
  }
}

java.util.stream.Collector

How collectors are used to transform the elements of a stream into a different kind of result is an example from Java 8 Streams. The code below applies a joining collector and a counting collector to the same list. The abstraction here is the interface Collector. Notice how all the examples till now use an interface as the strategy abstraction: the strategy pattern encourages the well known principle of programming to interfaces not implementations. Abstract classes can be used to implement strategies as well.

public class CollectorExample {
  public static void main(String[] args) {
    List<String> list = Arrays.asList("a", "b", "c");

    String s1 = list.stream().collect(Collectors.joining(","));
    System.out.println(s1); // will print a,b,c

    Long s2 = list.stream().collect(Collectors.counting());
    System.out.println(s2); // will print 3
  }
}

java.util.zip.Checksum

The code below computes the checksum of a given file using two different algorithms: CRC32 and Adler32. CheckedInputStream is the class that can use different algorithms to compute the checksum of an input stream. Imagine what would mean to create a new checksum algorithm: a new class that implements the interface Checksum (the abstraction strategy in this case). The strategy pattern allows us to add new functionality (new algorithms) by creating new classes and keeping the existing classes unchanged. Hey, this is Open Close Principle, isn’t it?

public class ChecksumExample {

  public static void main(String[] args) throws IOException {
    File f = new File("c:/file.txt");

    try (CheckedInputStream cis = new CheckedInputStream(
        new FileInputStream(f), new CRC32())) {
      readFile(cis);
      System.out.println(cis.getChecksum().getValue());
    }

    try (CheckedInputStream cis = new CheckedInputStream(
        new FileInputStream(f), new Adler32())) {
      readFile(cis);
      System.out.println(cis.getChecksum().getValue());
    }
  }

  static void readFile(InputStream is) throws IOException {
    byte[] buffer = new byte[1024];
    // we just need to read the file to compute the checksum
    while (is.read(buffer) >= 0) ;
  }
}

java.io.FileFilter

The code below uses lambda expressions for filtering files from a directory. Lambdas allows us to write more concise code and maybe the strategy pattern is harder to see here. There are two different filter strategies used, one for filtering out TXT files and one for XML files. The strategy abstraction is represented by the functional interface FileFilter. We really don’t see here the concrete strategy implementations as in previous examples, but, hey, this is the beauty of lambda expressions!

public class FileFilterExample {
  public static void main(String[] args) {
    File file = new File("c:/");
    File[] files;

    files = file.listFiles(path -> path.getName().endsWith(".txt"));
    printFiles(files, "txt files:");

    files = file.listFiles(path -> path.getName().endsWith(".xml"));
    printFiles(files, "xml files:");
  }

  private static void printFiles(File[] files, String title) {
    System.out.println(title);
    Arrays.stream(files)
        .map(file -> file.getName())
        .forEach(System.out::println);
  }
}

Conclusion

Strategy is a basic pattern and can be used to solve many problems, as we’ve seen in these Java SE examples. Do an exercise and try to find some more and leave a comment below. You can find all the code examples in this post here: https://github.com/cipmar/blog. If you want to learn about design patterns, there is a book that, at least for me, worked very well when I started as a software developer: Design Patterns Explained. Others recommend another book, which is good as well: Head First Design Patterns.

2 Comments

  1. Silviu

    Hi Marius, can you write a blog about Bridge design pattern ? I don’t think I fully understood it and certainty never seen it used in projects.

    Reply
    • marius.ropotica@gmail.com

      GoF definition of the Bridge Pattern is pretty cryptic, hard to grasp. Will try to explain it in a future blog post, with real examples.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *