-
Notifications
You must be signed in to change notification settings - Fork 2
Stubs: Explained
Normally, an implementation of an abstract class or interface is created by making a new type that extends from that abstract class or interface. The writer then adds all the logic for the implementation directly into the new type.
For Example, StreamWriter
implements (is a subclass of) TextWriter
. TextWriter
, as an abstract class, expects its subclass to provide certain members. The implementation for those members is contained directly in the StreamWriter
class.
This is the usual way to provide an implementation for an abstract class or interface.
A Stub is a class that implements an interface or extends from an abstract class, but explicitly does not provide any implementing logic. Instead, it exposes public delegate members, so that implementations can be injected at runtime.
This layer of indirection, if used incorrectly, makes code more complex. However, using Stubs can be a lightweight alternative to creating custom types for one-off scenarios. This makes them useful for creating Null Objects, test doubles, and implementations of small, helper interfaces. In general, you use a Stub to replace a full object when the full object is not required or not desirable.
Below are a few example scenarios where using Stubs can help make code cleaner.
Consider System.Windows.Input.ICommand
. WPF uses commands regularly, and provides a special implementation of ICommand
called RoutedCommand
that it uses heavily. But despite this specialized implementation, most WPF types are designed to accept any implementation of ICommand
.
One possible use of ICommand
is to provide an implementation for a Button
. When the Button
is clicked, it can either call a Click
event handler (usually implemented inside a UserControl
or Window
that owns the Button
), or it can call the Execute
method on an ICommand
(usually part of a ViewModel, allowing for a better separation between the UI and the business logic). ICommand.CanExecute
gets called to check if the Button
should even be enabled, and the ICommand
provides an event handler called CanExecuteChanged
that the Button
will automatically listen to in order to determine when to check CanExecute
again.
Overall, this is all very well designed. However, if your application needs many commands, you'll need to create a new implementation of ICommand
for every command you need. This can cause an explosion of types in your assembly, or worse, it can cause extremely complex dependency scenarios if several of your commands use the same data.
At some point, you may decide to make a reusable Command, one that accepts delegates in the constructor or as properties, and then calls those delegates as implementations for CanExecute
and Execute
. It probably also has a method called RaiseCanExecuteChanged
to allow the owner of the object to raise the event when needed. Maybe you name this class DelegateCommand or RelayCommand. Searching the internet will give you exactly these names, along with implementations from other people who felt this was useful enough to share.
Congratulations! You've just created a Stub. IDisposable
and IComparer<>
are other common .Net interfaces where developers commonly want a Stub in real code. You likely have small interfaces of your own, or small interfaces in common frameworks or tools that you use, and having automatic Stubs for these can help keep related code close together.
The newer version of C# include the ?.
operator, useful for simplifying code where the object you're using may be null
. However, how much simpler could your code be if you just never had to deal with null? Tony Hoare, the inventor of ALGOL W, called the null reference his 'billion dollar mistake.'
Here's code written before C#6:
return source != null ? source.CalculateResult() : -1;
Here's code written using C#6:
return source?.CalculateResult() ?? -1;
You probably think this looks much better. But wait, what if we had a special version of source available that encoded that extra bit of information about how the application should behave if there is no source object? This is usually called a NullObject, and it makes your code look like this:
return source.CalculateResult();
This is clearly the cleanest of the options, especially if there are other places where you have to do similar operations of conditionally returning the -1. You could easily write a custom type that provides this null implementation and name it something like NullSource
. But given the simplicity of the logic, you could also use a stub. Below is the setup for a stub object that can act as a NullObject in this case:
source = new StubSource
{
CalculateResult = () => -1
};
In this way, you can cross "make null implementation" off your todo list as well: the Stub implementation functions out-of-the-box by returning default values from every method and property, and can be quickly adjusted to fit whatever custom scenario you need.
You may have heard of Moq, or NSubstitute, or FakeItEasy. Each of these frameworks exist to assist in the development of Unit Tests by allowing you to easily replace real dependencies with fake implementations, allowing you to test specific units of code in isolation, independent of the behavior of the rest of your application. These can be very helpful, and have a number of features that make writing tests easier. Unfortunately, they have some downsides:
- Some frameworks require extension methods on basically everything, which can greatly reduce the helpfulness of Intellisense when writing unit tests.
- Setting up a custom implementation can be quite verbose.
- Use of dynamic proxies make them unsuitable for use in non-testing scenarios.
The Stubs created by AutoImplement have less testing specific features, but using Stubs for both your production code and unit tests can help unify your codebase and help all your code be more similar.