Come faccio a strutturare il mio codice per evitare un accoppiamento stretto tra le mie classi genitore e figlio?

6

Sto usando un prodotto di terze parti che ha una classe che posso estendere per fornire un nuovo modo per quel prodotto di ottenere dati (il prodotto è Sitecore ma non penso che sia super rilevante per la domanda).

Hanno una classe DataProvider che posso estendere:

public class Sitecore.DataProvider {
    public ItemDefinition GetItemDefinition();
    public VersionUriList GetItemVersions();
    // etc
}

Ho accesso a molte fonti di dati, quindi ho creato alcune classi di "accesso dati":

public class MyCode.IDataAccessor<TTypeOfData> {
    TTypeOfData GetDataItem(int key); 
    IEnumerable<int> GetDataKeys();
}
public MyCode.WWWDataAccessor : IDataAccessor<WebPages>
public MyCode.CSVDataAccessor : IDataAccessor<Spreadsheets>

Ho una classe che mappa gli ID dai dati di origine agli ID nel sysem di terze parti:

public class MyCode.DataMapper { /* .... */ }

Ho alcune classi che estendono DataProvider e consentono al sistema di terze parti di accedere ai miei dati:

// these override a lot of methods from Sitecore.DataProvider
public MyCode.WWWDataProvider : DataProvider { /* .... */ }
public MyCode.CSVDataProvider : DataProvider { /* .... */ }

Un sacco di codice è comune tra WWWDataProvider e CSVDataProvider, quindi ovviamente ho bisogno di suddividere quel codice da qualche parte invece di duplicarlo. Tutti i metodi ruotano attorno all'accesso ai metodi in un dato IDataAccessor o DataMapper.

L'opzione uno è di avere tutti questi metodi su una classe base astratta "MyCode.MyBaseDataProvider" che estende Sitecore.DataProvider. Le classi child possono impostare IDataAccessor e DataMapper su quella classe nel loro costruttore, con qualcosa come base(new WWWDataAccessor(), new DataMapper()) . Questo sembra piuttosto complicato in quanto la classe genitore e le classi figlie sono strettamente accoppiate. Sto anche pensando che dovrei "favorire la composizione sull'ereditarietà" solo nel caso in cui avessi bisogno di cambiare le mie classi figlio DataProvider in modo tale che usino una logica diversa.

L'opzione due è di lasciare l'abstract della classe base e fornire una varietà di metodi che delegano ad altri oggetti come appropriato, ad es. Potrei avere un metodo abstract TTypeOfData GetDataItem(int key); in quella classe base, e quindi le classi figlie semplicemente lo implementano con return this.dataAccessor.GetDataItem(key) . Ma questo sembra complicato in quanto tutte queste classi fanno esattamente la logica di quali metodi chiamare su quegli altri oggetti, quindi dovrò semplicemente riscrivere quella logica per ciascuno dei bambini - e duplicherò tutto il resto metodi sugli oggetti "helper" come wrapper sottili nelle classi child.

L'opzione tre , l'opzione che sto seguendo attualmente, è di avere una classe di "implementazione" separata. Nella mia classe figlia faccio this.implementation = DataProviderImplementation(dataAccessorObj, mapperObj); quindi ho molte sostituzioni come:

public override IDList GetChildIDs(ItemDefinition parentItem, CallContext context)
{
    if (implementation.CanProcessParent(parentItem))
    {
        return implementation.GetChildIDs(parentItem, context);
    }
    return base.GetChildIDs(parentItem, context);
}

Non esiste alcuna possibilità di utilizzare un modello factory per creare un'istanza di queste classi; il sistema di terze parti mi richiede di dargli un nome di classe completo e solo argomenti stringa in un file di configurazione XML, quindi ho bisogno sia delle classi WWWDataProvider che CSVDataProvider per eseguire il lavoro di istanziazione delle classi rilevanti IDataAccessor e DataMapper.

C'è un modo migliore per farlo? C'è un modello che dovrei esaminare?

    
posta George 27.10.2016 - 06:20
fonte

1 risposta

3

Capisco la tua prudenza. Un'altra opzione, certamente in C #, è che se si hanno classi che hanno gli stessi metodi o proprietà, devono implementare un'interfaccia (se la classe fa qualcosa) o ereditare da una classe base se è un modello, che definisce questi metodi e proprietà comuni e quindi creare i metodi di estensione per quell'interfaccia / astratto. Dovresti essere in grado di rimuovere completamente questi metodi condivisi dai tuoi oggetti. Mi piace molto questo metodo perché mantiene molti dei tuoi metodi di "utilità" in classi di estensione separate e fuori dai tuoi oggetti. Per favore non confondere una classe di estensione con l'estensione di una classe, non mi riferisco all'ereditarietà. Inoltre, sii cauto a non abusare di questo metodo. Se un particolare elemento di funzionalità non è comune, non c'è davvero molto bisogno di astrarlo in un metodo di estensione. Quando i metodi di estensione sono sovrautilizzati, possono spesso causare più danni che benefici.

E esempio di una classe di estensione

public abstract class BasePerson
{
    DateTime DOB { get; set; }
}

public class Person : BasePerson
{
}

public class Employee : BasePerson
{
    public StartDate { get; set; }
}

public static class Extensions
{
     public int GetAge(this BasePerson obj)
     {
        return (DateTime.Now - obj.DOB).Years;
    }
}

E per usarlo

var p = new Person { DOB = DateTime.Now.AddYears(-20); };

var age = p.GetAge();

var e = new Employee { DOB = DateTime.Now.AddYears(-20); }
age = e.GetAge();

Il metodo di estensione GetAge () è disponibile per entrambe le classi concrete (Person, Employee) perché entrambi ereditano da BasePerson

    
risposta data 27.10.2016 - 09:58
fonte

Leggi altre domande sui tag