DI: Factory pattern to the rescue

Technical Dec 12, 2021
dependency injection with help of factory pattern

Introduction

In this article I’ll try to showcase how factory pattern can help us to loosen our code dependency to achieve a better structured/designed software. As you probably know dependency injection can help us to develop better software which has more separation of concerns and many more benefits that comes with it.

All that aside back to the main topic, what has factory pattern got to do with it?

Problem

Lets assume that we have class A thats depended on class B, we declare our dependency on an abstraction of which class B provides the implementation lets call the abstraction B’, so that our code is not depended on a concrete implementation and we inject the B class as the implementation while/after instantiation of an object of type A, in this way we have loosen our dependency.

But what if class A needs to instantiate unknown number of objects of type B at a some point?

Solution

Here factory pattern comes to work, in class A we declare our dependency to factory class for type B’ which helps us to generate as many object of type B as we want and whenever we want.

Example

The example has been update in 2024, as previous example was not clear enough.

Lets say we are developing a simple multi-threaded file system search that lunches a new thread per sub-directory.
Here our SearchApp class is dependent to Thread class (yeah, not a perfect example).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.io.File;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class SearchApp {

    public static void main(String[] args) {
        var app = new SearchApp();
        var results = app.search("/home/user/Projects", "java");
        System.out.printf("Found %d match(es)\n", results.size());
        results.stream()
                .forEach(System.out::println);
    }

    public List<String> search(String dir, String fileName) {
        var results = new ConcurrentLinkedQueue<String>();
        var workers = new ConcurrentLinkedQueue<Thread>();
        this.find(dir, fileName, workers, results);
        workers.stream()
                .forEach(this::join);
        return results.stream().toList();
    }

    private void join(Thread thread) {
        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private void find(String dir, String fileName, Queue<Thread> workers, Queue<String> results) {
        File folder = new File(dir);
        File[] items = folder.listFiles();
        if (items != null) {
            for (File file : items) {
                if (file.isDirectory()) {
                    var worker = new Thread(() -> this.find(file.getPath(), fileName, workers, results));
                    worker.start();
                    workers.add(worker);
                } else if (file.getName().contains(fileName)) {
                    results.add(file.getPath());
                }
            }
        }
    }
}

As you can see at line 39, the new keyword is the red flag, as it’s making our SearchApp and Thread classes tightly coupled.
Ok now let’s use factory pattern to fix this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.io.File;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ThreadFactory;

public class SearchApp {
    private final ThreadFactory factory;

    public SearchApp(ThreadFactory factory) {
        this.factory = factory;
    }

    public static void main(String[] args) {
        ThreadFactory factory = r -> Thread.ofVirtual().unstarted(r);
        var app = new SearchApp(factory);
        var results = app.search("/home/user/Projects", "java");
        System.out.printf("Found %d match(es)\n", results.size());
        results.stream()
                .forEach(System.out::println);
    }

    public List<String> search(String dir, String fileName) {
        var results = new ConcurrentLinkedQueue<String>();
        var workers = new ConcurrentLinkedQueue<Thread>();
        this.find(dir, fileName, workers, results);
        workers.stream()
                .forEach(this::join);
        return results.stream().toList();
    }

    private void join(Thread thread) {
        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private void find(String dir, String fileName, Queue<Thread> workers, Queue<String> results) {
        File folder = new File(dir);
        File[] items = folder.listFiles();
        if (items != null) {
            for (File file : items) {
                if (file.isDirectory()) {
                    var worker = this.factory.newThread(() -> this.find(file.getPath(), fileName, workers, results));
                    worker.start();
                    workers.add(worker);
                } else if (file.getName().contains(fileName)) {
                    results.add(file.getPath());
                }
            }
        }
    }
}

Now if you look at highlighted lines, you see by declaring our dependency to the factory class we no longer are dependent on a concrete implementation*.
With this approach we can change the dependency implementation as we did change to virtual threads (line 15).

While the Thread class is not an interface, it’s the technic that matters and can be used elsewhere.

End

Let me know your thoughts.