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#):
1 | // 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.
Design with SAM
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.
SAM in Java
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:
1 | interface Action<T> { |
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):
1 | interface Action<T> { |
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 IComparer
and Comparison
defined below:
1 | public interface IComparer<in T> |
we can easily implement conversion function both ways: converting IComparer
to Comparison
is trivial as IComparer.Compare
is-a Comparison
while conversion the other way around would require a wrapper:
1 | public class AdhocComparer<T>: IComparer<T> |
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.
Design logging framework with SAM
If you ever used NLog
or log4net
you can imagine a ILoggerFactory
and ILogger
interfaces to be declared like this:
1 | public interface ILoggerFactory |
Right? With 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 Exception
.
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.
Reduce to SAM
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:
1 | ILogger Logger(Type type) => Logger(type.FullName); |
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:
1 | public interface ILoggerFactory |
Single. Abstract. Method.
Although, you want all the convenience methods to be available. Right? Right.
Extending SAM
I can think of three obvious ways to deliver them to the potential user:
- Wrapper class: a new class, let’s say
ExtendedLoggerFactory
, which takesILoggerFactory
in 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 sayLoggerFactoryBase
(yup, a kitten just died somewhere). - Extension methods: extension methods taking
ILoggerFactory
asthis
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 ExtendedLoggerFactory
leaving 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 Where
or 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:
1 | public interface ILoggerFactory |
Single method on the interface, and some extension methods, which are just redirecting calls to that interface.
Reducing ILogger to SAM
ILogger
is quite big. It is also quite repetitive:
1 | public interface ILogger |
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
,Func<string>
andstring, 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:
1 | public enum Severity |
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:
1 | public interface ILogger |
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):
1 | void Log(Severity severity, string message); |
Isn’t the second one equivalent to calling first one with string.Format(...)
(or with $"..."
)?
1 | Log(Severity.Error, pattern, arg1, args2, ...); |
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 (string.Format
or $"..."
) 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:
1 | public interface ILogger |
NOTE: Exception.ToString
is Func<string>
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):
1 | public static class MoreLoggerExtensions |
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)
.
Implementing adapters for NLog
So we need to implement these two interfaces:
1 | public interface ILoggerFactory |
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:
1 | private static LogLevel ToLogLevel(Severity severity) |
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:
1 | var level = ToLogLevel(severity); |
There is another way to do deferred expansion, though. It involves small wrapper class (again!) taking Func<string>
and calling it in .ToString()
:
1 | private class DeferredToString |
Now, we could use it like this:
1 | logger.Log(ToLogLevel(severity), "{0}", new DeferredToString(builder)); |
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:
1 | public class NLogLoggerFactory: ILoggerFactory |
Done. Now NLog is compatible with our facade.
Full implementation
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:
Summary
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:
1 | public interface ILoggerFactory |
All the methods are still available, though:
Further with SAM
You can go further with SAM by using just functions. Please read second part here.