If you need exactly one Thing list on your system, you should ask yourself what these things are. Are they elements that can be customized before each run? Are they things that will change as the user executes the program? Perhaps these things should be stored as records in a database, even light in memory, such as Apache Derby or NoSQL database.
Even if you really have a fixed set of elements, you should consider using a dependency injection and list vending system in the Singleton area instead of using a hard singleton. Thus, you can replace the list in test classes by changing the configuration.
If you find that the latter is confused, think about it. Suppose you have a class that processes Things , say, keeping a static list in it. Sort of:
public class ThingCatalog { private static final List<Thing> things = new ArrayList<>(); public ThingCatalog() {
Now you can make the ThingCatalog class singleton; you saw how to do it. Make the constructor private and create a static getInstance method. You will be tempted to write
public class TreasureGenerator { private ThingCatalog things = ThingCatalog.getInstance();
What happens if you want to write unit test for a method in this class that doesn't use things? You don't need things, so you really don't need ThingCatalog. Fortunately, you are stuck with it.
You can fix this by providing the TreasureGenerator method setThingCatalog:
public void setThingCatalog(ThingCatalog things) { this.things = things; }
Of course, you only have one ThingCatalog, so that doesn't help much. But if you had an interface that ThingCatalog could implement:
public interface ThingVendor { List<Thing> getThings(); Thing getThingById(int id); }
and all your classes used ThingVendor instead of ThingCatalog, you could replace it in your tests.
Here is a more business example. You are writing a financial program, and you need to print today's date when checking. It is typical to write code like:
String recipient = ...; String accountOwner = ...; BigDecimal amount = ...; String accountNumber = ...; Date today = new Date(); printCheck(...);
Now someone asks you: "Can your program handle jumps correctly?" Fourteen years ago, the question may have concerned 2000. How would you test this? You are stuck today in this method.
Instead, you write an interface called DateGenerator :
public interface DateGenerator { Date today(); } public class TodayGenerator implements DateGenerator { public Date today() { return new Date(); } } public class LeapDayGenerator implements DateGenerator { public Date today() { Calendar cal = Calendar.getInstance(); cal.set(2016, FEBRUARY, 29);
Your class will have a setDateGenerator method, and you usually use TodayGenerator. In your jump test, you will use the LeapDayGenerator.
A dependency injection system automates these processes. What you learn with experience, if you stay with computing, is that objects do not need to know how to configure themselves. Other parts of the project should stick objects together.