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

2. Factory methods

3. Static classes

4. Singleton

5. Dependency Injection Container

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)

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:

  1. Helper class with no dependencies
  2. Helper class with dependencies (for example, depends on the network or uses another class)
  3. Single-instance class with state (with or without dependencies)
  4. 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

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:

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:

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:

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:

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:

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:

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

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.

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:

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:

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:

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:

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.

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.

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:

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

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:

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:

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.

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

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:

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:

1. A factory can be a convenient way to store and inject dependencies.
2. A factory is great when you need to add a certain logic on each object creation. For example:

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:

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:

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:

instantiation summary table
Subscribe to get post updates and: Performance Optimizations in C# .NET: 10 Best Practices