A Solution Without Spring

In this section, we are going to add a second spam detector and introduce a better setup using the Factory pattern.

The Factory pattern is a standard software development pattern and helps in creating objects without knowing the exact implementation which will be created and how it is created.

When working with factories and multiple implementations of a service, you will create a common interface, which each of the implementation must extend.

So, let’s extract it from the SimpleSpamDetector. It will look like:

public interface SpamDetector {

    boolean containsSpam(String value);

}

Next, the SimpleSpamDetector must extend it by:

public class SimpleSpamDetector 
        implements SpamDetector {

    // rest omitted
}

Now, we create the factory and move the initialization of the SimpleSpamDetector to it:

public class SpamDetectorFactory {

    public static SpamDetector getInstance(
      String[] args
    ) throws IOException {
        List<String> spamWords 
          = new ArrayList<String>();

        if (args.length == 2) {
            spamWords = Files.readAllLines(
              Paths.get(args[1])
            );
        }

        return new SimpleSpamDetector(
          spamWords
        );
    }
}

By using factories, you have multiple options of how to get parameters. The usual ways are:

  • pass a global config object along; the main part of the application is responsible for creating it
  • each factory has its own config files
  • using static code blocks and hard-wired parameters

They do work, and it depends on your context which way to choose. However, we won’t go deeper into these as it is not necessary to understand Spring. Nonetheless, I can tell you it ‘s not a good idea to mix them; I once worked on a project where all three ways were actively used in a single application. It was no fun and time consuming to find out where to configure something.

For this example, we keep it simple and just use the arguments from the main method and pass them along. It acts as a global config.

In our SpamCheckerApplication we will use the Factory now like:

public static void main(String[] args)
throws Exception {

    SpamDetector spamDetector 
      = SpamDetectorFactory.getInstance(args);

    System.out.println(
      spamDetector.containsSpam(args[0])
    );

}

In the next step, we are adding a second spam detector and assume it will do a remote check. We are not actually going to implement the check, just the base construct for showing a glimpse of the rising complexity.

public class RemoteSpamDetector 
       implements SpamDetector {

    public RemoteSpamDetector(
      String url,
      String username,
      String password
    ) {
        // omitted, not needed for explanation
    }

    public boolean containsSpam(String value) {
        // make the remote call
        return false;
    }

}

The dummy RemoteSpamDetector needs three parameters to work, a URL, a username, and a password. We will create it in the SpamDetectorFactory as well and define, if the application retrieves more than two arguments, we should do a remote check and use the additional arguments for the RemoteSpamDetector.

public static SpamDetector getInstance(
  String[] args
) throws IOException {

    if (args.length <= 2) {
        List<String> spamWords 
          = new ArrayList<String>();
        spamWords = Files.readAllLines(
          Paths.get(args[1])
        );
        return new SimpleSpamDetector(
          spamWords
        );
    }

    return new RemoteSpamDetector(
      args[1],
      args[2],
      args[3]
    );
}

Remember, it looks different in a real world application following this pattern. You wouldn’t rely on command line args like that. Anyway, it is enough to grasp the concept and see where we are heading to.

Imagine now, how this will end if you add more services and implementations to our application. You will have a bunch of factories and config files. If you move the initialization to the application starter now, you will be basically on your first step towards IoC. However, your services still depend on the factories and are thus still coupled to the initialization of the other services.

Of course, you can implement it in a cleaner and maintainable way. However, the main disadvantage is that you have to start all over again for your next application.

This is where Spring Core with its Dependency Injection rescues us. It takes over our application initialization, gets rid of the factories, and provides a runtime for loading and coupling our services. And we can use it over and over again without reinventing it each time.

In the next section, we migrate our application to Spring.

Complete and Continue  
Discussion

0 comments