You are better off with SAM

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// C#
public interface IComparer<T>
{
int Compare(T alpha, T bravo);
}

public interface IEnumerable<T>
{
IEnumerator<T> GetEnumerator();
}

// Java
public interface Runnable {
void run();
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Action<T> {
void run(T item);
}

private static <T> void forEach(Iterable<T> collection, Action action) {
for (T item : collection) {
action.run(item);
}
}

private static void printAll(Iterable<String> strings) {
forEach(
strings,
new Action<String>() { // <-- starts here...
@Override
public void run(String item) {
System.out.println(item);
}
} // <-- ...and ends here
);
}

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
2
3
4
5
6
7
8
9
10
11
12
13
interface Action<T> {
void run(T item);
}

private static <T> void forEach(Iterable<T> collection, Action action) {
for (T item : collection) {
action.run(item);
}
}

private static void printAll(Iterable<String> strings) {
forEach(strings, item -> System.out.println(item)); // <-- starts and ends here :-)
}

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
2
3
4
5
6
public interface IComparer<in T> 
{
int Compare(T x, T y);
}

public delegate int Comparison<in T>(T x, T y);

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
2
3
4
5
6
7
8
9
10
11
12
public class AdhocComparer<T>: IComparer<T>
{
private readonly Comparison<T> _comparison;

public AdhocComparer(Comparison<t> comparison) {
_comparison = comparison;
}

public int Compare(T x, T y) => _comparison(x, y);
}

// for some reason syntax highlight sometimes fails

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public interface ILoggerFactory 
{
ILogger Logger(string name);
ILogger Logger(Type type);
ILogger Logger<T>();
ILogger Logger();
}

public interface ILogger
{
void Trace(string message);
void Trace(string pattern, params object[] args);
void Trace(Func<string> builder);
void Trace(Exception error, string message);
void Trace(Exception error, string pattern, params object[] args);
void Trace(Exception error, Func<string> builder);
void Debug(string message);
void Debug(string pattern, params object[] args);
void Debug(Func<string> builder);
void Debug(Exception error, string message);
void Debug(Exception error, string pattern, params object[] args);
void Debug(Exception error, Func<string> builder);
void Info(string message);
void Info(string pattern, params object[] args);
void Info(Func<string> builder);
void Info(Exception error, string message);
void Info(Exception error, string pattern, params object[] args);
void Info(Exception error, Func<string> builder);
void Warn(string message);
void Warn(string pattern, params object[] args);
void Warn(Func<string> builder);
void Warn(Exception error, string message);
void Warn(Exception error, string pattern, params object[] args);
void Warn(Exception error, Func<string> builder);
void Error(string message);
void Error(string pattern, params object[] args);
void Error(Func<string> builder);
void Error(Exception error, string message);
void Error(Exception error, string pattern, params object[] args);
void Error(Exception error, Func<string> builder);
void Fatal(string message);
void Fatal(string pattern, params object[] args);
void Fatal(Func<string> builder);
void Fatal(Exception error, string message);
void Fatal(Exception error, string pattern, params object[] args);
void Fatal(Exception error, Func<string> builder);
}

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
2
3
ILogger Logger(Type type) => Logger(type.FullName);
ILogger Logger<T>() => Logger(typeof(T));
ILogger Logger() => Logger(new StackTrace().GetFrame(1).GetMethod().DeclaringType);

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
2
3
4
public interface ILoggerFactory 
{
ILogger Logger(string name);
}

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:

  1. Wrapper class: a new class, let’s say ExtendedLoggerFactory, which takes ILoggerFactory in constructor and delivers additional functionality.
  2. 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).
  3. Extension methods: extension methods taking ILoggerFactory as this

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface ILoggerFactory 
{
ILogger Logger(string name);
}

public static class LoggerFactoryExtensions
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static ILogger Logger(this ILoggerFactory factory) =>
factory.Logger(new StackTrace().GetFrame(1).GetMethod().DeclaringType);
public static ILogger Logger(this ILoggerFactory factory, Type type) =>
factory.Logger(type.FullName);
public static ILogger Logger<T>(this ILoggerFactory factory) =>
factory.Logger(typeof(T));
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public interface ILogger 
{
void Trace(string message);
void Trace(string pattern, params object[] args);
void Trace(Func<string> builder);
void Trace(Exception error, string message);
void Trace(Exception error, string pattern, params object[] args);
void Trace(Exception error, Func<string> builder);
void Debug(string message);
void Debug(string pattern, params object[] args);
void Debug(Func<string> builder);
void Debug(Exception error, string message);
void Debug(Exception error, string pattern, params object[] args);
void Debug(Exception error, Func<string> builder);
void Info(string message);
void Info(string pattern, params object[] args);
void Info(Func<string> builder);
void Info(Exception error, string message);
void Info(Exception error, string pattern, params object[] args);
void Info(Exception error, Func<string> builder);
void Warn(string message);
void Warn(string pattern, params object[] args);
void Warn(Func<string> builder);
void Warn(Exception error, string message);
void Warn(Exception error, string pattern, params object[] args);
void Warn(Exception error, Func<string> builder);
void Error(string message);
void Error(string pattern, params object[] args);
void Error(Func<string> builder);
void Error(Exception error, string message);
void Error(Exception error, string pattern, params object[] args);
void Error(Exception error, Func<string> builder);
void Fatal(string message);
void Fatal(string pattern, params object[] args);
void Fatal(Func<string> builder);
void Fatal(Exception error, string message);
void Fatal(Exception error, string pattern, params object[] args);
void Fatal(Exception error, Func<string> builder);
}

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> and 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum Severity 
{
Trace, Debug, Info, Warn, Error, Fatal
}

public interface ILogger
{
void Log(Severity severity, string message);
void Log(Severity severity, string pattern, params object[] args);
void Log(Severity severity, Func<string> builder);
void Log(Severity severity, Exception error, string message);
void Log(Severity severity, Exception error, string pattern, params object[] args);
void Log(Severity severity, Exception error, Func<string> builder);
}

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
2
3
4
5
6
7
public interface ILogger 
{
void Log(Severity severity, string message);
void Log(Severity severity, string pattern, params object[] args);
void Log(Severity severity, Exception error);
void Log(Severity severity, Func<string> builder);
}

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
2
void Log(Severity severity, string message);
void Log(Severity severity, string pattern, params object[] args);

Isn’t the second one equivalent to calling first one with string.Format(...) (or with $"...")?

1
2
Log(Severity.Error, pattern, arg1, args2, ...);
Log(Severity.Error, string.Format(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface ILogger 
{
void Log(Severity severity, Func<string> builder);
}

public static class LoggerExtensions
{
public static void Log(
this ILogger logger, Severity severity, string message) =>
logger.Log(severity, () => message);
public static void Log(
this ILogger logger, Severity severity, string pattern, params object[] args) =>
logger.Log(severity, () => string.Format(pattern, args));
public static void Log(
this ILogger logger, Severity severity, Exception error) =>
logger.Log(severity, error.ToString);
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static class MoreLoggerExtensions
{
<# foreach (var level in new[] { "Trace", "Debug", "Info", "Warn", "Error", "Fatal" }) { #>
#region <#= level #>

public static void <#= level #>(this ILogger logger, string message) =>
logger.Log(Severity.<#= level #>, message);
public static void <#= level #>(this ILogger logger, Func<string> builder) =>
logger.Log(Severity.<#= level #>, builder);
public static void <#= level #>(
this ILogger logger, string pattern, params object[] args) =>
logger.Log(Severity.<#= level #>, pattern, args);
public static void <#= level #>(this ILogger logger, Exception error) =>
logger.Log(Severity.<#= level #>, error);

#endregion
<# LF(level != "Fatal"); } #>
}
<#+ void LF(bool condition = true) { if (condition) WriteLine(""); } #>

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
2
3
4
5
6
7
8
9
public interface ILoggerFactory 
{
ILogger Logger(string name);
}

public interface ILogger
{
void Log(Severity severity, Func<string> builder);
}

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
2
3
4
5
6
7
8
9
10
11
12
private static LogLevel ToLogLevel(Severity severity) 
{
switch (severity) {
case Severity.Trace: return LogLevel.Trace;
case Severity.Debug: return LogLevel.Debug;
case Severity.Info: return LogLevel.Info;
case Severity.Warn: return LogLevel.Warn;
case Severity.Error: return LogLevel.Error;
case Severity.Fatal: return LogLevel.Fatal;
default: return LogLevel.Off;
}
}

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
2
3
var level = ToLogLevel(severity);
if (logger.IsEnabled(level))
logger.Log(level, builder());

There is another way to do deferred expansion, though. It involves small wrapper class (again!) taking Func<string> and calling it in .ToString():

1
2
3
4
5
6
private class DeferredToString 
{
private readonly Func<string> _builder;
public DeferredToString(Func<string> builder) { _builder = builder; }
public override string ToString() { return _builder(); }
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class NLogLoggerFactory: ILoggerFactory
{
public ILogger Logger(string name) =>
new NLogLogger(LogManager.GetLogger(name));
}

public class NLogLogger: ILogger
{
private readonly Logger _logger;

public NLogLogger(Logger logger) { _logger = logger; }

public void Log(Severity severity, Func<string> builder)
{
var level = ToLogLevel(severity);
if (_logger.IsEnabled(level))
_logger.Log(level, builder());
}
}

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
2
3
4
5
6
7
8
9
public interface ILoggerFactory
{
ILogger Logger(string name);
}

public interface ILogger
{
void Log(Severity severity, Func<string> builder);
}

All the methods are still available, though:

intellisense

Further with SAM

You can go further with SAM by using just functions. Please read second part here.