Encapsulation in OOP – Using Lambdas for Better Encapsulation

encapsulationlambdaobject-oriented

Just as many people believe tenaciously in small functions, some people believe lambdas should only contain small code fragments.

An often overlooked advantage of lambdas however, is using them you can encapsulate behavior which you would otherwise have to make available to the entire class.

Isn't it is a true advantage to maintain proper encapsulation, regardless of line count?
Which possible disadvantages of using anonymous functions with a lot of lines of code am I overlooking?


The following is an actual example. argumentsMatch does very specific argument matching, highly dependent on the behavior of the function in which it is defined. The following code IMHO follows the Single Responsibility Principle. Moving argumentsMatch to a private method would result in it only being called from within this method.

/// <summary>
///   Get the first found matching generic type.
///   The type parameters of the generic type are optional.
///   E.g. Dictionary<,>
///   When full (generic) type is known (e.g. Dictionary<string,string>),
///   the "is" operator is most likely more performant,
///   but this function will still work correctly.
/// </summary>
/// <param name = "source">The source for this extension method.</param>
/// <param name = "type">The type to check for.</param>
/// <returns>
///   The first found matching complete generic type,
///   or null when no matching type found.
/// </returns>
public static Type GetMatchingGenericType( this Type source, Type type )
{
    Type[] genericArguments = type.GetGenericArguments();
    Type rawType = type.IsGenericType ? type.GetGenericTypeDefinition() : type;

    // Used to compare type arguments and see whether they match.
    Func<Type[], bool> argumentsMatch
        = arguments => genericArguments
            .Zip( arguments, Tuple.Create )
            .All( t => t.Item1.IsGenericParameter || // No type specified.
                       t.Item1 == t.Item2 );

    Type matchingType = null;
    if ( type.IsInterface )
    {
        // Traverse across all interfaces to find a matching interface.
        matchingType = 
            (from t in source.GetInterfaces()
             let rawInterface = t.IsGenericType ? t.GetGenericTypeDefinition() : t
             where rawInterface == rawType &&
                   argumentsMatch( t.GetGenericArguments() )
             select t).FirstOrDefault();
    }
    else
    {
        // Traverse across the type, and all it's base types.
        Type baseType = source;
        while ( baseType != null && baseType != typeof( object ) )
        {
            Type rawCurrent = baseType.IsGenericType
                ? baseType.GetGenericTypeDefinition()
                : baseType;
            if ( rawType == rawCurrent )
            {
                // Same raw generic type, compare type arguments.
                if ( argumentsMatch( baseType.GetGenericArguments() ) )
                {
                    matchingType = baseType;
                    break;
                }
            }
            baseType = baseType.BaseType;
        }
    }

    return matchingType;
}

Best Answer

I think the important point here is code locality: Code that should be read together should be close together.

My rule of thumb is: If both the called (lambda) function and the caller can be read, understood, maintained, tested (and possibly, but not necessarily, reused) separately, then extract the lambda function into a separate function or class. If the two are so closely tied together that you can't really make sense of one without knowing what the other does, then keep them as closely together as possible; lambdas are perfect for this, if the language you use doesn't allow nested functions.