Home > Software design >  How do I reduce duplication by moving method to base class?
How do I reduce duplication by moving method to base class?

Time:09-30

I have this class.

public class ComponentMapper : MapperBase
{
    public static PageComponent Map(object value)
    {
        return value switch
        {
   
            _ => HandleUnknownType(value),
        };
    }

    private static HandleUnknownType(object value)
    {

            throw new ArgumentException($"{nameof(ComponentMapper)} - failed to map object of type {value.GetType()}. Component={nameof(value)}");

    }
}

The HandleUnknownType is quite similar for all implementations, which is why I am considering moving it to the base class, MapperBase, problem is though that nameof(ComponentMapper) is used to state that the class that uses this function does know the type.

But if I move it to the base class, I will not be able to know the class that is implementing it.

I would like the HandleUnknownType method to stay static, to avoid creating uneeded objects

How do I solve this issue?

CodePudding user response:

One way to solve this is via the "Curiously recurring template pattern", but it is not without drawbacks.

To do that you'd change the base class to be generic, where the generic type is constrained to itself, like so:

public abstract class MapperBase<TMapper, TComponent> where TMapper: MapperBase<TMapper, TComponent>
{
    protected static TComponent HandleUnknownType(object value)
    {
        throw new ArgumentException($"{typeof(TMapper).Name} - failed to map object of type {value.GetType()}. Component={nameof(value)}");
    }
}

Note that this also uses a type argument, TComponent, for the type to be returned from HandleUnknownType().

With that, all your classes that derive from MapperBase<TMapper, TComponent> will have to repeat the pattern, like so:

public class PageMapper : MapperBase<PageMapper, PageComponent>
{
    public static PageComponent Map(object value)
    {
        return value switch
        {
            _ => HandleUnknownType(value)
        };
    }
}

public class OtherMapper : MapperBase<OtherMapper, OtherComponent>
{
    public static OtherComponent Map(object value)
    {
        return value switch
        {
            _ => HandleUnknownType(value)
        };
    }
}

Given that code, then calls to the static method MapperBase.HandleUnknownType() will output the correct exception message:

public static class Program
{
    public static void Main()
    {
        try
        {
            PageMapper.Map("test");
        }

        catch (Exception e)
        {
            // PageMapper - failed to map object of type System.String. Component=value
            Console.WriteLine(e.Message);
        }

        try
        {
            OtherMapper.Map("test");
        }

        catch (Exception e)
        {
            // OtherMapper - failed to map object of type System.String. Component=value
            Console.WriteLine(e.Message);
        }
    }
}

public class PageComponent  {}
public class OtherComponent {}

Try it on DotNetFiddle

As I said, this is not without drawbacks. For example, there's nothing stopping the programmer from messing it up by specifying a different generic type than the class being declared, for example:

public class SomeOtherMapper : MapperBase<PageMapper, PageComponent> // WRONG!
{
}
  • Related