Come combinare questi metodi di riempimento e creazione o utilizzare generici in fabbrica?

1

Oggetto dominio "Contragent"

Diciamo che ho una gerarchia di classi:

public class BaseContragent
{
   public int Id { get; set; }
}

public class PersonContragent : BaseContragent
{
   public string FirstName { get; set; }

   public string LastName { get; set; }
}

public class CompanyContragent : BaseContragent
{
   public string CompanyName { get; set; }
}

public class BankCompanyContragent : CompanyContragent
{
   public string BankBic { get; set; }
}

public class LocalBankCompanyContragent : BankCompanyContragent 
{
   public string NationalBankIdentificator { get; set; }
}

In realtà, questa gerarchia ha molte classi, ha molta logica aziendale e tutti i campi sono presi dalla logica del dominio aziendale, quindi non posso cambiarla solo a causa di un servizio esterno scomodo.

Contragent
- PersonContragent
-- PersonWithoutCitizenshipContragent
-- CitizenshipContragent
--- LocalCitizenshipContragent
--- ForeignCitizenshipContragnet
- CompanyContragent
-- BankCompanyContragent
--- LocalBankCompanyContragent
etc.

Ora, il sistema esterno invia richieste per creare oggetto

Ho una richiesta per creare un oggetto da un sistema esterno, che assomiglia a questo:

public class CreateRequest
{
    public int Id { get; set; }
    public string Name { get; set; } // default for base, value for subclasses
    public int Type { get; set; } // 0 - for Base, 1 for Person, 2 for Company

    // many other fields belonging to different types
}

Esiste un solo tipo di richiesta di creazione per tutti i tipi di Contragent , e quale dovrei utilizzare dipende dal valore Type .

Per isolare il mio sistema da questo strano sistema esterno ho implementato una sorta di strato anti-corruzione con le fabbriche che creano oggetti di questa gerarchia:

public abstract class BaseContragentFactory 
{
    protected abstract Contragent Create();

    protected abstract Fill(Contragent ca, CreateRequest createRequest);

    public Base CreateAndFill(CreateRequest createRequest)
    {
        var ca = Create();
        Fill(ca, createRequest);
        return ca;
    }
}

public class SimpleContragentFactory : BaseContragentFactory  
{
    protected virtual Contragent Create() 
    {
        return new Contragent();
    }

    protected virtual void Fill(Contragent ca, CreateRequest createRequest)
    {
        ca.Id = createRequest.Id;
    }
}

public class PersonContragentFactory : SimpleContragentFactory 
{
    protected override Contragent Create()
    {
        return new PersonContragent();
    }

    protected override void Fill(Contragent ca, CreateRequest createRequest)
    {
        var pca = ca as PersonContragent;
        if (pca == null)
            throw new InvalidOperationException("...");

        base.Fill(pca, createRequest);

        string[] nameParts = createRequest.Name.Split(";"); // Firstname;Lastname

        pca.FirstName = nameParts[0];
        pca.LastName = nameParts[1];
    }
}

public class CompanyContragentFactory : SimpleContragentFactory 
{
    protected override Contragent Create()
    {
        return new CompanyContragent();
    }

    protected override void Fill(Contragent ca, CreateRequest createRequest)
    {
        var bca = ca as CompanyContragent;
        if (bca == null)
            throw new InvalidOperationException("...");

        base.Fill(bca, createRequest);

        bca.CompanyName = createRequest.Name;
    }
}

Ho fabbriche del genere per quasi tutti i tipi di Contragent .

public void Create(CreateRequest request)
{
    BaseContragentFactory factory;
    switch (request.Type)
    {
        case 0:
          factory = new SimpleContragentFactory();
          break;
        case 1:
          factory = new PersonContragentFactory();
          break;
        case 2:
          factory = new CompanyContragentFactory();
          break;
        default:
          throw new InvalidOperationException("...");
    }

    Contragent ca = factory.CreateAndFill(request);
}

Tuttavia, non mi piace che questo codice abbia cast, controlli e che la sicurezza del tipo sia mantenuta solo da uno sviluppatore.

Quindi, ho due domande su come migliorare la sicurezza e la leggibilità del tipo di codice:

Domanda 1: come combinare i metodi Create e Fill ? Come rendere questa classe avere un solo metodo. Il problema è che devo essere in grado di creare documenti solo una volta nella classe ereditata dall'alto e quindi chiamare i metodi di riempimento base .

Domanda 2: esiste un modo per utilizzare i generici per rendere queste fabbriche più sicure dal punto di vista dei tipi e accettare valori generici tipizzati? Può essere, qualcosa del genere:

public abstract class BaseContragentFactory<T> where TContragent : Contragent, new()
{
    protected TContragent Create() 
    {
        return new TContragent();
    }
    // ...
    
posta Yeldar Kurmangaliyev 24.03.2017 - 05:18
fonte

2 risposte

1

In base alla tua domanda aggiornata, ti suggerisco di progettare in basso:

public class BaseContragent
{
    public int Id { get; set; }
}

public class PersonContragent : BaseContragent
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class CompanyContragent : BaseContragent
{
    public string CompanyName { get; set; }
}

public class CreateRequest
{
    public int Id { get; set; }
    public string Name { get; set; } // default for base, value for subclasses
    public int Type { get; set; } // 0 - for Base, 1 for Person, 2 for Company
}

public interface IContragentFactory
{
    BaseContragent Create(CreateRequest createRequest);
}

public class SimpleContragentFactory : IContragentFactory
{
    public BaseContragent Create(CreateRequest createRequest)
    {
        var pca = new BaseContragent();
        Fill(pca, createRequest);

        return pca;
    }

    public static void Fill(BaseContragent ca, CreateRequest createRequest)
    {
        ca.Id = createRequest.Id;
    }
}

public class PersonContragentFactory : IContragentFactory
{
    public BaseContragent Create(CreateRequest createRequest)
    {
        var pca = new PersonContragent();
        Fill(pca, createRequest);

        return pca;
    }

    public static void Fill(PersonContragent pca, CreateRequest createRequest)
    {
        SimpleContragentFactory.Fill(pca, createRequest);

        string[] nameParts = createRequest.Name.Split(';'); // Firstname;Lastname

        pca.FirstName = nameParts[0];
        pca.LastName = nameParts[1];
    }
}

public class CompanyContragentFactory : IContragentFactory
{
    public BaseContragent Create(CreateRequest createRequest)
    {
        var bca = new CompanyContragent();

        Fill(bca, createRequest);

        return bca;
    }

    public static void Fill(CompanyContragent bca, CreateRequest createRequest)
    {
        SimpleContragentFactory.Fill(bca, createRequest);

        bca.CompanyName = createRequest.Name;
    }
}

public class FactoryClient
{
    public void Create(CreateRequest request)
    {
        IContragentFactory factory;
        switch (request.Type)
        {
            case 0:
                factory = new SimpleContragentFactory();
                break;
            case 1:
                factory = new PersonContragentFactory();
                break;
            case 2:
                factory = new CompanyContragentFactory();
                break;
            default:
                throw new InvalidOperationException("...");
        }

        BaseContragent ca = factory.Create(request);
    }
}

Elimina l'ereditarietà e utilizza la "composizione" utilizzando il metodo di riempimento statico. In questo caso, lo sviluppatore chiama il metodo parent% factory Fill , ma non ci sono cast di tipo.

    
risposta data 24.03.2017 - 08:05
fonte
1

I generici non aiutano molto in questa situazione, poiché tutte le tue classi ereditano dalla stessa classe base.

Tuttavia, ti suggerisco di avere troppe classi.

Stai cercando di aggirare il condizionale, 'se voglio che x lo costruisca in questo modo' Ma è impossibile perché hai diversi parametri di costruzione.

Se rifattori il codice per inserire la costruzione in un costruttore, richiedendo l'appropriata CreateRequest e inserendo un caso di commutazione sul tipo di CreateRequest nel metodo factory, il tuo codice sarà considerevolmente più breve e più semplice

In alternativa puoi provare a usare un contenitore DI e configurarlo tramite la configurazione xml per imitare la tua fabbrica.

Questo ti darà la soluzione in stile 'no code change', tuttavia, farà semplicemente la stessa cosa con la riflessione sotto il cofano. Ad esempio, esamina il tipo di CreateRequest in arrivo, analizza la logica da xml e instanciate l'oggetto mappato tramite il constuctor.

dato che tutti stanno facendo un esempio di codice, tenterò uno sul mio telefono.

public class Person : BaseContragrent {
    public Person(CreateRequestPerson cr) : base(cr)
    {
         this.FirstName = cr.FirstName;
         .....
     }
}

public class Factory {
    public BaseContragent  Create(CreateRequest cr) 
    {
         if(cr is CreateRequestPerson)
         {
              return new Person(cr);
         }
         .....
    }
}

Sarei tentato di andare ancora oltre e perdere le CreateRequests e passare solo più parametri e un enum di tipo.

    
risposta data 24.03.2017 - 07:30
fonte

Leggi altre domande sui tag