Search code examples
c#.netdesign-patternsarchitecturefactory-pattern

Factory CreateInstance argument method not necessary for a specific subtype


I have a factory class and CreateInstance method

CreateInstance(EntityModel.TipoEntitaTipoParametroEntita tipoParametroEntita, IParametroEntitaMultiValoreDataSourceProvider parametroEntitaMultiValoreDataSourceProvider)

The factory can instantiate two differents subtypes depending on the value of tipoParametroEntita.TipoCampo.IdTipoCampo

The point is the second argument of CreateInstance (parametroEntitaMultiValoreDataSourceProvider) is used only for creating instance of TipoEntitaTipoParametroEntitaMultiValore
whereas is not used in creating instance of TipoEntitaTipoParametroEntitaSingoloValore

public class TipoEntitaTipoParametroEntitaFactory : ITipoEntitaTipoParametroEntitaFactory
{
    /// <summary>
    /// Creates an instance of TipoEntitaTipoParametroEntitaSingoloValore or  TipoEntitaTipoParametroEntitaMultiValore 
    /// </summary>
    public TipoEntitaTipoParametroEntita CreateInstance(EntityModel.TipoEntitaTipoParametroEntita tipoParametroEntita, IParametroEntitaMultiValoreDataSourceProvider parametroEntitaMultiValoreDataSourceProvider)
    {
        if (tipoParametroEntita.TipoCampo.IdTipoCampo == (int)EntityModel.Enum.TipoCampo.CampoLibero ||
            tipoParametroEntita.TipoCampo.IdTipoCampo == (int)EntityModel.Enum.TipoCampo.CampoLiberoMultiLinea)
        {
            return new TipoEntitaTipoParametroEntitaSingoloValore(tipoParametroEntita);
        }

        if (tipoParametroEntita.TipoCampo.IdTipoCampo ==
            (int)EntityModel.Enum.TipoCampo.DropdownListQueryDataSource ||
            tipoParametroEntita.TipoCampo.IdTipoCampo ==
            (int)EntityModel.Enum.TipoCampo.DropdownListTableDataSource)
        {
            return new TipoEntitaTipoParametroEntitaMultiValore(tipoParametroEntita,
                parametroEntitaMultiValoreDataSourceProvider);
        }

        return null;

    }
}

I'm doubtful about this adopted pattern as I always need to pass an instance of IParametroEntitaMultiValoreDataSourceProvider even when it is not necessary and moreover someone reading the signature of the method might be led to think that for creating any type of TipoEntitaTipoParametroEntita an instance of IParametroEntitaMultiValoreDataSourceProvider is required.

What would be a better approach? Two distinct factories? Only one factory and two CreateInstance (one returning TipoEntitaTipoParametroEntitaSingoloValore and the other TipoEntitaTipoParametroEntitaMultiValore)?

I both of the cases I should already know which factory or which CreateInstance to call so I should check tipoParametroEntita.TipoCampo.IdTipoCampo every time in advance. But I'd like to keep this logic only in one place.


Solution

  • From a functional programming perspective, I use to look at the Visitor pattern when dealing with the so called "algebraic data types" i.e. different subtypes. Anyway I'm not always a fan of this approach, since it can be difficult at the beginning.

    Therefore I'll give only the basic idea, so that you can quickly decide if you're interested. Also notice that the goal here is to write a code with function signatures such that errors can be spot at compile time, as opposite to runtime.

    Now in a nutshell the classical way to achieve this, by leveraging a C# language type check feature, is to define a new visitor including all the different overrides of your CreateInstance with signatures:

    public IEntitaTipoParametroEntita CreateInstance(SubType1 subType1) 
    { 
       // ...
    }
    
    public IEntitaTipoParametroEntita CreateInstance(SubType2 subType2) 
    {
        // ...
    }
    

    where each subtype should have its own

    IEntitaTipoParametroEntita accept(CreateVisitor visitor) 
    {
        visitor.CreateInstance(this);
    }
    

    So that you can avoid if and switch and similar error prone subtype checking syntaxes and simply instantiate the specific visitor instead and just pass it to any subtype to be processed.

    DB tables

    If there is significant commonality between subtypes, then splitting the subtypes out into separate physical tables is probably of little value.

    Therefore, as far as DB design is concerned, "table per hierarchy" (which utilizes a type discriminator column to hold type information) seems to better fit your example: it enables polymorphism by denormalizing the SQL schema. To instruct Entity Framework to use this strategy, all that is required is to derive a class from the DbContext class, and add a DBSet property for the supertype, whilst not adding DBSet properties for subtypes.

    Reading links