NOTE: This is part one of two part article. You can read second part here. To be honest, this is where interesting stuff starts.
This introduction is for hard core OO developers (mostly C# but maybe Java as well) who are trying to add FP to their skillset. If you are FP developer, you most likely know all those things.
SAM is the term introduced in Java 8 and means “Single Abstract Method”. It’s describes an interface with only one method. Of course, we had them before (both in Java and C#):
but they became more visible in Java 8, as they were used to implement very important language feature. We will talk about this in few moments.
Designing your system with SAMs has some quantifiable advantages.
First of all, it helps with Single Responsibility Principle and Interface Segregation Principle. Don’t get me wrong, it is still possible to do it wrong, and create single method with multiple responsibilities. Actually, some of the solutions presented in this article are not the purest, but they are, I hope at least, move in the right direction.
Second, your SAM interfaces are easier to implement. Did you ever try replace a component and found out that interface you need to implement has 50 methods? It just smells like “Oh, we had this God Object with some random methods and doing everything, but someone told us we need interfaces so we extracted every single possible method, job’s done”. They usually never get implemented again.
Third, they are easier to mock. I’m kind of reiterating second point here: they are easier to mock as there is only one method to fake, stub or mock (it’s not the same, I know, but whatever you call it, it’s still easier). You may say, that for bigger interfaces, you still need to mock only one method for a particular test. But which one, I would ask? Oh, the one which given test actually uses. Really? So now, you are making assumptions how exactly tested method works, instead of providing dependencies are checking if it satisfies acceptance criteria? Why testing it at all then? It is also a recipe for brittle tests. Change implementation a bit and your test will start to fail because your function under test calls different overload now (for example, the one with timeout passed as
TimeSpan rather than
int). Design it with SAM at there will be only one method to mock.
Oh, I forgot to mention, Functional Programmers also have a name for SAM, they call it… a function.
Java has anonymous classes for injecting behaviour since 1997 (Java 1.1), so in Java world the concept is not new at all. Using them was just very clunky:
If you look closely, the anonymous class implementing
Action interface in
printAll is what we would call lambda in C#. It is just completely wrist-unfriendly.
When lambdas were added, they wanted all those functions to support lambda. Who doesn’t like lambdas? Right? But they couldn’t just change them, as Java is all about backwards compatibility. Duplicating every method (to accept anonymous class or lambda) would be a huge task. They decided, and so far it seems like a smart move, that they won’t change the interfaces, they will just modify the compiler to generate those, a little bit inflated, anonymous classes for you (NOTE: this isn’t exactly true, Java 8 implementation of lambdas is actually quite optimized):
From inside of
forEach method nothing has changed, it still “thinks” it works with class implementing
Action interface, but this class has been generated on the fly (well, again, not exactly true, but good-enough) from lambda.
This mechanics immediately breaks, of course, when expected interface has more than one (abstract) method, as we can only implement one with lambda function.
Anyway, what’s important to remember is the fact that interface with single abstract method is technically a function, and a function is technically an interface with single abstract method, for example, with
Comparison defined below:
we can easily implement conversion function both ways: converting
Comparison is trivial as
Comparison while conversion the other way around would require a wrapper:
I will point this fact again later, but please note that this is not a coincidence that converting to FP is usually trivial, while conversion to OO requires a little wrapper. Usually (at least) 4 lines: a class declaration, a field to store the function, a constructor to take function as argument, and single abstract method calling this function. That’s exactly what Java compiler does.
Nevertheless, SAM is function, function is SAM.
If you ever used
log4net you can imagine a
ILogger interfaces to be declared like this:
ILoggerFactory we can get logger with given name, logger for given type, or for given type but passed as generic, and maybe a logger for current scope (most likely it will be a class). With
ILogger you can log messages of different severity (Trace, Debug, Info, etc.), and provide messages in few different ways: as a string, or maybe as a pattern to be expanded when logging, with or without
Anyway, great, let’s start implementing it. Method by method… Just joking.
It is one of those interfaces you should really ask: I really like all those methods, and I want them to be available but do I need to implement them all? No, you don’t.
Let’s start with
ILoggerFactory as it is much easier. You can notice that somewhere on the end, regardless which method on
ILoggerFactory we call, we end up in
Logger(string) as this is logger’s primary constructor and all other methods use it in some way:
There is nothing in their implementation which would require them to be re-implemented in every possible implementation of
ILoggerFactory. So, technically, this interface should be actually reduced to:
Single. Abstract. Method.
Although, you want all the convenience methods to be available. Right? Right.
I can think of three obvious ways to deliver them to the potential user:
- Wrapper class: a new class, let’s say
ExtendedLoggerFactory, which takes
ILoggerFactoryin constructor and delivers additional functionality.
- Abstract class: a class with a additional functionality implemented, but with
ILogger Logger(string)left as abstract. No interface, just base class, let’s say
LoggerFactoryBase(yup, a kitten just died somewhere).
- Extension methods: extension methods taking
For completeness, I have to say, that Scala has traits and implicit wrappers, while Java gives you default methods. It is outside of the scope this article, but worth googling (even if you are C# developer).
There are pros and cons to all of those solutions.
The wrapper class is probably the cleanest from OO perspective, but a little bit confusing and clunky: we implement
ILoggerFactory but pass
ExtendedLoggerFactory (alternatively, we can also pass
ILoggerFactory and rewrap it all the time). Most of the clunkiness of this solution disappears when using IoC container (we always inject
ILoggerFactory for library developers).
Abstract class does not have this problem but enforces specific base class, pulling all the dependencies with it. It’s a viable solution, but I tend to stay away from partially implemented abstract classes.
Extension methods are quite controversial, as your extension methods are not polymorphic at all. Once defined, they cannot be overridden. With some functions it is not a problem as there is not “other” implementation of LINQ
Select, but it is possible to get it too far. If you think your extension methods may be controversial “hide” them in some namespace with needs to be imported explicitly, for example:
LoggingFramework.Extensions. It you think they are just fine, put them into the same namespace as interface, let’s say
LoggingFramework.Core. If you think they are so great that everyone should use them day and night, stick them into
System.Linq (and add all the warning suppression directives). Definitely, this can make development very easy (because extension are always available thanks to IntelliSense), or it can make you very unpopular (for exactly the same reason). Use them wisely.
In this article I decided to go for extension methods. I think this approach is relatively pragmatic, never had problem with it in a field and even if there is a potential flaw, the net effect is positive. Actually, Reactive Extensions are using the same technique (just check what is available on
IObservable<T> interface with and without
System.Reactive namespace imported).
So, after this long introduction, the declaration of the interface and implementation of convenience methods:
Single method on the interface, and some extension methods, which are just redirecting calls to that interface.
ILogger is quite big. It is also quite repetitive:
Please note, that the methods here are Cartesian product of all choices we can make:
- 6 severity levels (Debug, Trace, Info, Warn, Error, Fatal)
- 2 ways to pass exception (with or without exception)
- 3 ways to format parameters (
string, params object)
So we have
6 x 2 x 3 = 36 methods.
Let’s move one of the choices out of the interface, and introduce
Severity enum. It will reduce this interface to just 6 methods:
It’s the same technique you might have used for database normalization.
Next, I think that doubling number of overloads, only because we can call it with or without
Exception is unnecessary, one dedicated overload for
Exception is enough:
Before we proceed it might be important to explain why these method pair exists (not only in our implementation, but in general, in all logging frameworks):
Isn’t the second one equivalent to calling first one with
string.Format(...) (or with
Well, effect is the same, but side effects are different. When we leave expansion to the logging framework it won’t be done if logging is disabled (let’s say in production we do not log
Severity.Trace). When doing string expansion yourself (
$"...") you do this before framework has a chance to decide if logging is enabled, so you do this even if expanded string is going to be swallowed, wasting CPU cycles and risking exception being thrown during this operation.
We already have deferral mechanism in this interface, though:
Func<string>. Actually, all those four overloads can be implemented using only one method on interface and convenience method on extender:
Of course, we want our convenience methods back. Let’s add them as extension methods, using T4 template engine this time (we could do this by hand, as this is generated only once, but using T4 is useful skill to have):
Note that these methods are not necessary, they do not add new features. They are just convenient redirects. They do not have to be implemented ever again as long as
ILogger has single abstract method
void Log(Severity severity, Func<string> builder).
So we need to implement these two interfaces:
to make NLog compatible with our logging facade. Let’s do it…
First thing which definitely needs to be addressed is the fact that NLog does not “understand” our
Severity. It uses its own enumeration called
LogLevel. We need to translate it:
Second thing is, NLog does not have an overload taking
Func<string>. It has the one taking a
string or a
string, params object. We can settle with the
string version and use:
There is another way to do deferred expansion, though. It involves small wrapper class (again!) taking
Func<string> and calling it in
Now, we could use it like this:
but, even if I actually like it more, it is not necessary and it has some negative performance implications. As I said before, the sole existence of this 4 line class is admission that OO design is not as expressive as FP.
Both ways, it will work just fine, and the implementation is:
Done. Now NLog is compatible with our facade.
So, let me reiterate, as might have disappeared in wall of text:
This is The Interface:
These are The Extensions, a set of functions we extracted from interface, as they don’t really need to be there:
The third part is The Convenience Layer, an even bigger set of functions, which do not added functionality but make typing easier:
Please note, that this is not a
.cs file, it is a
.tt file, I just used
.cs extension on gist to have syntax highlight. The expanded version of this template can be found here
The last part, most likely in different assembly (as it has third party dependency) is The Adapter, adapting actual implementation to our abstract interface:
We reduced the interface surface from 40 methods (36 + 4), to 2 without affecting functionality, all you need to implement (or fake) logging system is this:
All the methods are still available, though:
You can go further with SAM by using just functions. Please read second part here.