Software Engineering is very different from any other type of engineering. While in mechanical engineering or electrical engineering there’s a definite “right” and “wrong” way of doing things, in software there just isn’t. At least as far as software design is concerned.
10 software engineers can build the same product with completely different architectures, different programming models and different languages, and nobody will be right or wrong.
With that mindset, I’d like to tackle an old dilemma: Class instantiation. Which pattern do you use to create a class? Do you always use a new
statement? Do we still need to use Singleton or Factory? Should we always use dependency injection? How about static classes, are they truly evil?
This is an opinionated article, please take it in as such. But, I would love your feedback either way, even if you disagree.
Types of Instantiations
In 2019, software design evolved a little beyond the standard Factory/Singleton patterns that we learned in computer science degree. We’ve got inversion of control, static classes, and even extension methods.
Here are the options at our disposal :
All the examples are going to be written in C#, but it’s just as relevant to other object-oriented languages.
1.Regular instantiation
var instance = new MyClass();
instance.Foo();
2. Factory methods
class MyClassFactory
{
MyClass CreateInstance() // might be a static method
{
return new MyClass();
}
}
// ...
var instance = myClassFactory.GetInstance();
instance.Foo();
3. Static classes
MyClass.Foo();
4. Singleton
class MyClass
{
private static MyClass instance=null;
public static MyClass Instance
{
get
{
// I'm omitting the lock for example's sake
if (instance==null)
instance = new MyClass();
return instance;
}
}
}
// ...
MyClass.Instance.Foo();
5. Dependency Injection Container
// In application bootstrap
InjectionContainer.Add<myclass>();
// option 1:
MyClass myClass = InjectionContainer.Resolve<myclass>();
// option 2: MyClass is injected as a dependency in another class
public class SomeClass
{
public SomeClass(MyClass myClass)
{
myClass.Foo();
}
}</myclass></myclass>
In many cases, we will inject an interface and not the class itself (for test purposes and decoupling).
6. Extension methods (in special cases and in some languages)
public static class MyExtensions
{
public static int Foo(this String str)
{
// do stuff
}
}
// ...
string x = "Hello World";
x.Foo();
Types of Classes
We can’t claim one type of instantiation is better or worst because each serves different purposes. Something that would fit one type of class, won’t fit another. Let’s try to categorize class types then:
- Helper class with no dependencies
- Helper class with dependencies (for example, depends on the network or uses another class)
- Single-instance class with state (with or without dependencies)
- Multiple-instance class with state (with or without dependencies)
Helper classes would be classes that don’t have any state (data). For example, Math
is a helper class, but Person
with Name and Age fields isn’t.
That seems a strange way to categorize class types, but as far as instantiation is concerned, this is all that matters.
So what instantiation method first each class category?
Structure of this article
We’re going to over each combination of instantiation type and class category. That’s 24 combinations, so bear with me.
Along the way, we’ll talk about testing and mocking, dependencies, and general programming guidelines.
If you find yourself losing concentration, or you’re one of those people that likes to peek at the last pages of a book, there’s a cheat-sheet table in the end.
Let’s start with helper classes with no dependencies.
1. Helper classes with no dependencies
When a class has no state, we can define it as a “Helper class”. That means the class itself doesn’t represent any entity, but it helps other classes to achieve some functionality. For a class like that, it doesn’t matter if it has one instance or many.
A helper class with no dependencies has pure logic. For example, Math
in .NET is a helper class.
There’s usually no point in mocking such classes because there’s nothing to mock, it’s just logic. For example in Math
class, there’s no point to mock the Max
method or Sin
method.
Here’s An example of a helper class that adds functionality to arrays:
class ArrayHelper
{
public int FindLargestNumber(int[] array)
{
int largestSoFar = array[0];
for (int i = 1; i < array.Length; i++)
{
if (array[i] > largestSoFar)
largestSoFar = array[i];
}
return largestSoFar;
}
}
Now comes the dilemma – which type of instantiation should we use?
Let’s go over our options.
Option 1: Regular Instantiation
With regular instantiation, every time you will want to use the functionality, the code will look like this:
var arrayHelper = new ArrayHelper();
var largest = arrayHelper.FindLargestNumber(myArray);
If you don’t want to write that additional line of code, you can create an instance once and save _arrayHelper
as a member.
Pros: Simple to understand, simple to use. The class can be modified later to derive from another, implement an interface or to add dependencies.
Cons:
Less readable – Needs additional code each time (or save as member). Seems a waste to create a new instance every time.
Creating a new instance implies the class has a state, which can cause confusion.
Verdict: Regular instantiation is a good option if you’re creating an instance in just one place or very few places. If it’s a utility class that’s used in many place (like Math
), it’s not the best choice.
Option 2: Factory Methods
Here you would have a factory class that creates instances according to some logic:
public class FactoryOfArrayHelper
{
public static CreateArrayHelper()
{
// maybe some logic
return new ArrayHelper();
}
}
A factory is used to create multiple instances, which doesn’t fit helper classes because all instances are the same (it has no state).
One exception when a factory might be useful is when you want a different strategy according to some logic. For example, in day time you should use helper-class-A and in the night time you should use helper-class-B. In this case, the helpers would implement a common interface.
Verdict: Don’t use factory methods for helper classes, unless you need to implement a strategy.
Option 3: Static Classes
Helper classes don’t need more than one instance since they have no state. This leads to the question of whether they need an instance at all? Here’s an example:
class ArrayHelper
{
public static int FindLargestNumber(int[] array)
{
// same as before
}
}
//...
var largest = ArrayHelper.FindLargestNumber(myArray);
Static classes have a somewhat bad reputation. They can’t be mocked for tests, they can’t implement interfaces, and they act as GC Roots (in languages with a garbage collector) causing potential memory leaks. But, all that has nothing to do with helper classes that don’t need to be tested.
Pros:
Short syntax
Readable code
No waste on multiple instances, which can cause performance problems due to extra garbage collection.
Cons:
If your class will need to change from a static to a regular class, it will be relatively hard to refactor. This might happen if you need to include dependencies or implement an interface.
In some rare cases, you will need to mock your helper class to fake functionality. Static classes are notoriously hard to mock.
Precedence: .NET Framework itself uses static classes extensively. These would be the classes Math
, File
, Path
, and so on.
Verdict: If your class is going to stay a stateless helper class with no dependencies, a static class is a good choice.
Option 4: Singleton
A singleton is a great pattern to ensure there’s only a single instance of a class. It’s fallen a bit out of fashion, now that we have dependency injection frameworks, but it’s still viable in some cases. Especially if you have a small library where you don’t want to develop/include a dependency-injection mechanism.
For the purpose of a helper class, there’s really no point for a singleton pattern because there’s no point to ensure there’s a single instance.
Option 5: Dependency Injection Container
Dependency injection frameworks became very popular in recent years (many recent years). It serves several important purposes:
- Allows dependency inversion (inversion of control)
- Allows loose coupling – You can know the interface, but you don’t have to know the implementing class
- Single responsibility principle – Your class doesn’t have to worry about its dependencies. It just has to do its one job, trusting that someone will implement the missing pieces (the interfaces it communicates with).
In practice, dependency injection makes it easy for us to do these:
1. Easily inject mock classes for tests
2. Easily add dependencies to any class (usually just add a parameter to the constructor)
3. Easily inject different implementation (according to configuration or whatever) to the entire application.
Getting back to the problem at hand, should we be using dependency injecting for our helper class?
Since we never need to mock our class, and it doesn’t implement any interfaces, there’s not much point.
Verdict: Dependency injection for helper classes is possible, but doesn’t add any of the advantages dependency injection offers.
Option 6: Extension Methods
Extension methods allow to add functionality to a class from different places in code, even after the original class was already compiled. Here’s how we would do it in our example:
namespace MyExtensionMethods
{
public static class ArrayExtensions
{
public static int FindLargest(this int[] array)
{
int largestSoFar = array[0];
for (int i = 1; i < array.Length; i++)
{
if (array[i] > largestSoFar)
largestSoFar = array[i];
}
return largestSoFar;
}
}
}
//... in a different file:
using MyExtensionMethods; // namespace must be included
//...
var largest = myArray.FindLargest();
From first glance, this is a short and readable syntax. Though it can be confusing if you’re not familiar with extension methods.
A great example of this is LINQ in C#. All the method syntax is built on extension methods and it’s been one of the most popular programming features created in the last 10 years.
It’s important to use extension methods without additional dependencies, and when the extension method is relevant to the extended type. An example of a terrible extension method might be extending int
to update bank account balance:
int newBalance;
//...
newBalance.UpdateDatabase();
Getting back to the problem at hand – will extension methods be a good fit for helper classes with no dependencies?
Pros:
Great syntax
Help from the IDE intellisense
Cons:
Extending widely used types (like primitives) can become confusing in a big application.
When creating inappropriate extension methods (like newBalance.UpdateDatabase()
) you allow for potential major mistakes.
Verdict: Extension methods are great when the code is very relevant to the extending type.
2. Helper Classes with Dependencies
As mentioned before, helper classes have no state and it doesn’t matter whether there’s a single instance or multiple instances because all instances act the same. However, those classes might have dependencies. These might be the File System, Network, Databases, or other classes.
An example of such a class is the File
class in .NET. It’s a static class that can write to files and read from files. It has a dependency on the file system but no state.
As an example, let’s create a class that does time zone conversion. It has a dependency on another class that provides the machine’s time zone.
public class CurrentMachineTime
{
public int GetCurrentGMTTimeZone()
{
//...
}
}
public class TimeHelper
{
private readonly CurrentMachineTime _currentMachineTime;
public TimeHelper(CurrentMachineTime currentMachineTime)
{
_currentMachineTime = currentMachineTime;
}
public DateTime GetCurrentTimeFromNewYorkTime(DateTime timeInNewYork)
{
// new york time zone is GMT - 4 so we have to add 4
return timeInNewYork +
TimeSpan.FromHours(_currentMachineTime.GetCurrentGMTTimeZone() + 4);
}
}
The basic functionality is to calculate what time will be here when time in New York is X.
Dependencies
Before going further, let’s talk a bit about classes with dependencies. There are basically 2 ways you can work with dependencies:
1.Create-Yourself: You create a dependency yourself, either by creating an instance with new
or by calling another class’s factory method or singleton method. For example:
public void MyClass
{
OtherClass _dependency;
public MyClass()
{
_dependency = OtherClass.GetInstance();// in case of singleton
}
}
2. Dependency-Injection: The dependency is injected to you from outside. This is mostly done in the constructor or by having someone else set your class’s property. For example:
public void MyClass
{
OtherClass _dependency;
public MyClass(OtherClass dependency)
{
_dependency = dependency;
}
}
Dependendcy Injection doesn’t have to be done with a dependency injection infrastructure (container). As you see, just passing an instance in a constructor is also dependency injection.
For test purposes, it’s always best to use dependency injection.
Let’s go over our options to instantiate a helper class with dependencies:
Option 1: Regular Instantiation
In regular instantiation, with the dependency injection method, you will do something like this:
var timeHelper = new TimeHelper(currentMachineTime);
timeHelper.GetCurrentTimeFromNewYorkTime(timeInNewYork);
But where will you the currentMachineTime
come from?
By creating a new instance each time, you will have to take care to have the currentMachineTime
as well. Not very convenient, unless it’s done in a single place where TimeHelper
and its dependencies are both created.
If you’re creating the dependency yourself, regular instantiation is fine, but you’ll be paying the price when testing.
Verdict: Regular instantiation is a good approach if you have just one place (or very few places) to create the helper class.
Option 2: Factory Method
A factory method might be useful because it can be the one place to create all the dependencies, avoiding code duplication. If you’re not mocking this class, then it can be pretty convenient with a static
Factory:
static class MyClassFactory
{
private static TimeHelper _timeHelper;
public static TimeHelper GetInstance()
{
// You might want to add a lock here
if (_timeHelper == null)
_timeHelper = new TimeHelper(new CurrentMachineTime());
// Same instance can be used each time because it's a helper class
return _timeHelper;
}
}
// ...
var timeHelper = MyClassFactory.GetInstance();
This is convenient but terrible for tests.
If you do need tests, then you’ll need to inject a factory interface, and in that case, you might as well inject the dependency instead of using the factory.
Verdict: When there’s no need for tests, a factory method can be convenient to manage dependencies. If you do need to mock the helper class or its dependencies, a factory is not a good option.
Option 3: Static Classes
Static classes are usually not a great choice for anything with dependencies. As mentioned, they are notoriously hard to test.
Injecting dependencies to a static class is pretty much impossible. You can set a property from outside, but there’s no guarantee in code that another class won’t use the static class beforehand.
When no injections or tests are needed, it can be very convenient. For example, when your helper class depends on something like the file system.
static class FileHelperClass
{
public static void DeleteFilesWithSpecificExtension(string directory, string extension)
{
// ...
}
}
Verdict: Use static helper classes only when you don’t plan to test them or mock them to test other classes.
Option 4: Singleton
Like with helper classes that have no dependencies, there’s really no point for a singleton pattern because there’s no point to ensure there’s a single instance.
It can save garbage collection effort, but in almost all cases you’re better with regular instantiation, static classes or dependency injection.
Option 5: Dependency Injection Container
For any class that has dependencies, dependency injection is a great choice. This allows you to mock the class or its dependencies, and to easily change implementations.
// In application bootstrap
InjectionContainer.Inject<currentmachinetime>(Options.Singleton);
InjectionContainer.Inject<timehelper>(Options.Singleton);
// anywhere:
var instance = InjectionContainer.Resolve<timehelper>();
// alternatively, you can just add a parameter in any constructor that's also
// resolved by the IOC container (**inversion of control container** is another
// name for **dependency injection container**).</timehelper></timehelper></currentmachinetime>
Although it doesn’t really matter in terms of functionality, it’s best to set the injection to use a single instance each time.
Pros: Great for tests. Very convenient if you already got a dependency injection framework in place.
Cons: Requires to have a dependency injection mechanism.
Option 6: Extension Methods
Consider this code:
public static class TimeHelper
{
public DateTime GetCurrentTimeFromNewYorkTime(this DateTime timeInNewYork, CurrentMachineTime currentMachineTime)
{
/// ...
}
}
var newTime = nyTime.GetCurrentTimeFromNewYorkTime(currentMachineTime);
The syntax is decent but note that you’ll have to pass the dependencies as parameters – not very convenient.
For tests, it actually works well. Since the dependencies are passed as parameters, you’ll be able to pass mocks easily. The class itself (TimeMachine
) cannot be mocked (it’s static), but it doesn’t really matter because it’s just logic.
Verdict: Not very convenient, but can be a good fit in cases where the extended type is very relevant to the method.
3. Single-instance classes with state
Such classes can represent an Application State, Application Configuration or something similar. Something like that should be protected to have a single instance.
These classes can also be some big manager or service type of class, that orchestrates your application’s business logic.
They often need to be mocked for test purposes (if you do have tests).
Option 1: Regular instantiation
Usually, regular instantiation doesn’t make sense for a class that has only one instance. But, in special scenarios, in a carefully reviewed program this is a valid choice. You will create that one instance somewhere in the application bootstrap and pass it (inject it) to all the necessary services. For example:
public class Bootstrap
{
MySingleInstanceClass _instance;
public void InitApp()
{
_instance = new MySingleInstanceClass(new DependencyA(), ...);
_managerA = new ManagerA(_instance);
_managerB = new ManagerB(_instance);
// ...
}
}
This makes sense when you don’t want to use a dependency injection framework.
Testing is very easy with the above method. Since everything is injected, you can create your own InitApp
method that’s used just or tests and initializes mock instances.
I mentioned several times that you might not want a dependency injection framework. There are scenarios when this makes sense. If, for example, you’re creating a library and you don’t want to add an additional dependency.
Option 2: Factory Methods
A factory makes no sense for single instance classes.
Option 3: Static Classes
A static class with state has the same problems as mentioned before:
1. It can’t be (easily) mocked for tests.
2. You can’t inject dependencies to static classes.
Verdict: Use static helper classes only when you don’t plan to test them or mock them to test other classes. If your application has has a dependency injection framework, prefer dependency injection. I personally avoid using static classes that have a state.
Option 4: Singleton Pattern
A singleton is a very reasonable choice for single-instance classes. It ensures a single instance, available throughout the application and easily implemented.
It’s fallen a bit out of fashion in favor of dependency injection with some good reasons:
It’s hard to mock a singleton. It’s hard to inject dependencies to a singleton. A singleton can create dependencies on its own, but then everything becomes even harder to test.
Verdict: A singleton makes sense if you don’t have tests and don’t plan to add tests. If you do have tests and don’t want to use dependency injection, you can still mock singletons with some hacky code. For example, here’s one variation:
class MyClass : IMyClass
{
private static MyClass instance = null;
public static IMyClass Mock { get; set; }
public static Singleton Instance
{
get
{
if (Mock != null)
return Mock;
// You might want to add a lock here
if (instance==null)
instance = new MyClass();
}
return instance;
}
}
// When beginning a test:
MyClass.Mock = new MyMock();
I won’t judge you.
Option 5: Dependency Injection
All dependency injection frameworks have an option to create a single-instance class. If you already have such a network in place, this is a great choice.
InjectionContainer.Inject<myclass>(Options.Singleton);
// Either resolve in code or accept it in a constructor as a parameter
var myClass = InjectionContainer.Resolve<myclass>();</myclass></myclass>
Pros: You’ll be able to easily mock your class, inject other dependencies to your class, and pass the instance to other classes.
Cons: You need a widely used dependency injection in place. If you want to use your class from a place that doesn’t have access to the dependency injection container, then it will be a problem.
Option 6: Extension methods
It makes no sense to have extension methods in a class that has state. Extension methods exist just to extend the functionality of other classes.
4. Multi-instance class with state
Multiple instance classes always have state, otherwise there’s no point of them being multi-instanced. These can be Model classes like Customer
or ShoppingCart
. These also can be classes with logic inside like AlgorithmStep
.
They can have dependencies or not. For example Customer
might be a Plain Old CLR Objects (POCO) class, that has just fields and no dependencies (other than other POCO classes).
On the other hand, a class like AlgorithmStep
can have dependencies on application settings or a bunch of other things.
Option 1: Regular instantiation
For any multi-instance class, this is the de-facto methodology. Just create an instance and use it. For example:
public void CreateCustomer()
{
var customer = new Customer();
_customers.Add(customer);
}
You can inject dependencies in the creation. For example: var customer = new Customer(_emailValidationService)
Pros: Easy to use, easy to understand.
Cons:
1. When you have a lot of dependencies you want to add, this might prove to be hard.
2. When you want to add some logic to each new instantiation, then you’ll have to use other options, like a Factory.
Option 2: Factory
Although a factory was designed to create multiple instances, we don’t always need to use one. Here’s why we would use a factory instead of regular instantiation with new:
- A factory can be a convenient way to store and inject dependencies.
- A factory is great when you need to add a certain logic on each object creation. For example:
class CustomerFactory
{
public Customer CreateCustomer()
{
var customer = new Customer();
_database.Add(customer);
return customer;
}
}
We can place the same logic in Customer
constructor but it creates a very nice separation of concerns when using a factory.
As for tests, you should be able to mock the factory itself or mock the factory’s dependencies. A factory is a helper class, (with or without dependencies) so see above how to instantiate helper classes for tests.
Verdict: A factory shouldn’t be the default choice, but it’s a good choice for specific needs. For tests, you might need to mock the factory, which isn’t trivial.
Option 3: Static Classes
A static class can’t have multiple instances by definition, so it’s out of the question.
Option 4: Singleton
A singleton also can’t have multiple instances, so out of the question as well.
Option 5: Dependency Injection Container
With a dependency injection frameworks, you can write:
var myClass = InjectionContainer.Resolve<myclass>();</myclass>
This is useful when MyClass
has a lot of dependencies of its own. They will be injected automatically in the constructor by the dependency injection framework.
Another usage is when your class is required for each new instance of another class:
public void SomeClass
{
public SomeClass(MyClass instance)
{
// new instance of MyClass will be created for each new instance of SomeClass
}
}
For tests, this method is perfect. You will be able to mock whatever you need by providing a dependency injection bootstrap of your own in the tests.
Verdict: Dependency injection is useful for testing, to easily inject dependencies, and if your class is a dependency to other classes. When there’s no need, avoid using it because it provides less intuitive syntax and overhead.
Option 6: Extension Methods
Extension methods are static classes, so they can’t have multiple instances.
Summary
Software design is one of the most interesting parts of programming. It’s challenging and hard to get right. Everything I described above is something experienced developers “feel” intuitively. It takes time to develop that feel, but if you’re a junior developer, you’ll get there.
These guidelines are great food for thought but don’t really solve the dilemma, even if you accept them as the God-given truth. For one thing, you need to decide which category your class is going to be in the first place. Is it going to have state or not? Which dependencies will it have? Will you need to mock it for tests? This is is software engineering for you, and unlike electrical engineering, there are no hard truths.
I remind you that everything I wrote is my own take on this. In software design, there are no rules set in stone, and every case is different.
Here’s a summary of all the possible combinations: