Unificazione di due implementazioni tramite un'interfaccia

2

Ho un oggetto semplice come questo

  public class Book
    {
        public List Chapters { get; private set; }
        public TableOfContent BookTOC { get; set; }
        public string Identifier { get; private set; }
        public string Title { get; private set; }
        public string Publisher { get; set; }
        public string Rights { get; internal set; }
        public DateTime PublishingDate { get; internal set; }

        private IBookRepository _repository = null;

        public Book(IBookRepository bookRepository)
        {
            _repository = bookRepository;
        }

        public Book Load(string bookid)
        {
            //Load book from repository
        }

       public Chapter GetNextChapter(string chapterId)
        {
            //Load Chapter from repository
        }

    }

In questo dominio (in realtà in qualsiasi parte del mondo!), il libro ha capitoli e il libro ha ToC. Nella nostra applicazione, ci sarebbero due possibili implementazioni del repository, vale a dire che l'oggetto del libro può essere caricato da Xml o caricato dal database. Questi libri sono di dimensioni piuttosto grandi, vale a dire duecentoquattromila capitoli (Questo è tutto sui metadati del libro, il contenuto è separato ma fuori questione per ora)

Il mio problema è che quando il libro viene caricato dal database, voglio sfruttare le funzionalità del database di query e indici, ecc. Ad esempio, nel GetNextChapter, quando sto usando il repository del database, posso individuare solo il capitolo specifico e trovare dopo. Comunque nel caso di Xml, dato che non voglio attraversare xml più e più volte, mi piacerebbe tenerlo caricato in una volta inizialmente e la cache in upstream in modo da poter trovare il prossimo capitolo proprio dall'oggetto stesso. Come puoi vedere, queste due implementazioni sono diverse per natura, ma in qualsiasi momento useremo solo una di queste. Non sono in grado di trovare un'interfaccia uniforme di operazioni che entrambe queste implementazioni possano seguire. Diciamo che se decido di avere l'operazione GetNextChapter implementata in entrambi, quindi per il repository del database, ho solo bisogno dell'ID del capitolo, mentre nel repository Xml ho bisogno dell'intero oggetto libro o almeno dell'elenco dei capitoli (e anche quello non ha nulla da fare con xml in realtà perché è solo nel filtro di memoria).

Qualcuno può aiutare a decidere come uniformare questi due attraverso un'unica interfaccia con due implementazioni? O non sono destinati ad essere uniformati tramite una singola interfaccia e meritano un modo separato di implementazione?

Modifica Per chiarire ulteriormente, se aggiungo il metodo GetNextChapter nell'interfaccia, l'implementazione del database richiede solo il chapterID come parametro, mentre l'implementazione Xml richiede l'oggetto libro. Allo stesso modo il metodo LoadBook restituirà l'oggetto completo (con chatpers e toc) per XMLImplementation (in modo che non debba passare xml più volte) mentre restituirà solo oggetto parziale (senza capitoli e toc) nel caso di DatabaseImplementation (per ovvi motivi di prestazioni).

Quindi, come andranno questi due contro una singola interfaccia?

    
posta Subhash Dike 12.08.2015 - 23:00
fonte

6 risposte

2

OK, penso che alcune delle altre risposte tocchino su questo, ma lo spiegherò.

I costruttori non fanno parte dell'interfaccia, quindi puoi averne diversi per ogni repo.

public BookRepoXml(string xmlStringContainingAllBookData) //or a filename to the bookxml?
{
    this.cachedChapters = this.ParseXmlIntoChapterDictionary(xmlStringContainingAllBookData);
}

e

public BookRepoSql(string databaseConnectionString)
{
    this.dbConnectionIWillUseLater.Open(databaseConnectionString);
}

quindi entrambi i repository possono implementare lo stesso metodo

public Chapter GetChapterById(string id)
{
    return this.cachedChapters[id]
}

o

public Chapter GetChapterById(string id)
{
    var data = this.dbConnectionIWillUseLater.Execute("select * from chapter where..");
    return this.PopulateChapterFromData(data);
}
    
risposta data 07.07.2016 - 10:19
fonte
2

Scaccerei le tre responsabilità per un repository (libro di carico da XML, libro di carico da un database, libro di cache) in tre diverse implementazioni:

public interface BookRepository {
    Book getBookFromId(string id);
}

public sealed class XmlBookRepository : BookRepository {
    private readonly string path;

    public XmlBookRepository(string path) {
        this.path = path;
    }

    public Book getBookFromId(string id) {
        //open xml file from path
        //load the full book and returns it
    }
}

public sealed class DbBookRepository : BookRepository {
    private readonly string dbConn;

    public DbBookRepository(string dbConn) {
        this.dbConn = dbConn;
    }

    public Book getBookFromId(string id) {
        //query the db with the id
        //load the full book and returns it
    }
}

public sealed class CachedBookRepository : BookRepository {
    private readonly BookRepository origin;
    private readonly Dictionary<string, Book> cache;

    public DbBookRepository(BookRepository origin) {
        this.origin= origin;
        this.cache = new Dictionary<>();
    }

    public Book getBookFromId(string id) {
        if(!cache.ContainsKey(id)) {
            cache.Add(id, origin.getBookFromId(id));
        }
        return cache.Get(id);
    }
}

Con queste 3 implementazioni puoi comporre un repository xml in cache:

new CachedBookRepository(new XmlBookRepository("data.xml"));

o un semplice repository di database:

new DbBookRepository("connString");

e come bonus puoi anche creare un repository db memorizzato nella cache senza alcuno sforzo aggiuntivo:

new CachedBookRepository(new DbBookRepository("connString"));

o se si osserva che il repository xml memorizzato nella cache è over-engineering, è possibile rimuoverlo in una sola posizione senza influire su qualcos'altro nel codice:

new XmlBookRepository("data.xml");

Utilizzo:

BookRepository br = new CachedBookRepository(new XmlBookRepository("data.xml"));
Chapter c3 = br.getBookFromId("1").getNextChapter("2"); //will read the full book and cache it
Chapter c4 = br.getBookFromId("1").getNextChapter("3); //will read from the in-memory object
    
risposta data 11.07.2016 - 15:02
fonte
0

Se ho capito bene, il tuo libro è fondamentalmente un elenco di capitoli con metadati aggiuntivi e vuoi aggiungerlo in due modi per caricarlo attraverso un'interfaccia unificata. Ma a seconda della persistenza, l'operazione di get / load cambia.

if I add GetNextChapter method in the interface, Database implementation needs only the chapterID as parameter, whereas Xml implementation would need the book object.

Quindi, forza semplicemente che hai bisogno dell'oggetto del libro. Quindi implementare il get con uno dei dati (ID o elenco dei capitoli) a seconda del repository sottostante. Un metodo che otterrebbe un capitolo per id senza riferimento al libro non appartiene alla classe Book , è un costruttore / caricatore Chapter separato.

Inoltre, non è chiaro per me se vuoi implementare GetNextChapter come un iteratore (il capitolo corrente è all'interno della classe, nessun parametro id) o un accessor id casuale (io chiamerei questo GetChapterById, con id come parametro). Ma presumo sia un Iterator e appartenga a Book .

the LoadBook method will return full object (with chatpers and toc) for XMLImplementation (so that I don't have to traverse xml over and over) whereas it will only return partial object (without chapters and toc) in case of DatabaseImplementation (for obvious performance reasons).

Gli oggetti parziali non mi sembrano molto corretti. Avete diverse possibilità, ma quello che farei è che l'implementazione del repository di libri supporti operazioni get / load astratte. BookRepository supporta inevitabilmente distinzioni implementative a seconda che si tratti di XML o DB, quindi suona logico mettere qui l'astrazione. Che cosa accadrebbe alla tua classe di libri è che ListChapter non sarebbe un campo di Book , sarebbe solo un campo del tuo XMLBookRepository .

    
risposta data 07.07.2016 - 10:03
fonte
-1

Can anyone help in deciding how do I uniform these two through a single interface having two implementation? Or are they not meant to be uniformed via a single interface and deserve a separate way of implementation?

In breve, il principio di Segregazione dell'interfaccia è importante perché se una determinata classe diventa la nuova hotness, si vorrebbe usarla ovunque.

Il client non dovrebbe essere forzato a utilizzare un'interfaccia se non ne ha bisogno.

    
risposta data 14.07.2016 - 16:02
fonte
-2

Questo è un esempio perfetto per un uso dell'interfaccia o, se possibile, di un'API pubblica. All'interno del tuo dominio Book , sei dipendente da un'implementazione di IBookRepositoy , a cui interagirai tramite la sua API pubblica (i suoi metodi pubblici).

Ora devi solo seguire il Principio di Responsabilità Unica e Separazione delle Preoccupazioni per ottenere un buon design. Quello che vuoi qui è avere più implementazioni di IBookRepository , che non sono note al tuo oggetto dominio Book . Potresti dire XMLBookRepository e DatabaseRepository sarebbero le tue 2 diverse implementazioni.

Detto questo, l'implementazione utilizzata in entrambi questi repository dovrebbe rimanere nascosta dal dominio Book, poiché interagirà tramite l'API pubblica, l'interfaccia IBookRepository .

Una volta che è sul tavolo, è necessario implementare entrambi i repository nel modo in cui sono necessari. Nota, stiamo parlando di 2 differenti implementazioni; questo perché, come lei stesso ha dichiarato:

when I am using database repository, I can locate just the specific chapter and find next of it. However in case of Xml, since I don't want to traverse xml over and over again, I would like to keep it loaded at one time initially and the cache it upstream so that I can find the next chapter just from the object itself.

Questa è una chiara indicazione che hai bisogno di più implementazioni.

    
risposta data 13.08.2015 - 00:56
fonte
-2

Semplice. Crea Book classe astratta e implementa il caricamento nelle classi concrete.

Per prima cosa, non mischiare i metodi "repository-like" in Book , ma tienilo solo nel repository.

public abstract class Book
{
    public List<Chapter> Chapters { get; private set; }
    public TableOfContent BookTOC { get; set; }
    public string Identifier { get; private set; }
    public string Title { get; private set; }
    public string Publisher { get; set; }
    public string Rights { get; internal set; }
    public DateTime PublishingDate { get; internal set; }

    public abstract Chapter GetNextChapter(string chapterId);
}


public class DBBookDepository
{
    public Book LoadBook(string identifier)
    {
        return new DBBook(this);
    }
}

public class DBBook : Book
{
    private readonly DBBookDepository _bookRepo;

    public DBBook(DBBookDepository bookRepo)
    {
        _bookRepo = bookRepo;

        // load stuff from DB
    }

    public override Chapter GetNextChapter(string chapterId)
    {
        // either do SQL command or call repository
    }
}

public class XmlBookRepository
{
    public Book LoadBook(string path)
    {
        XmlDocument xmlDocument = new XmlDocument();
        xmlDocument.Load(path);
        return new XmlBook(path, xmlDocument);
    }
}

public class XmlBook : Book
{
    private readonly string _path;
    private readonly XmlDocument _xml;

    public XmlBook(string path, XmlDocument xml)
    {
        _path = path;
        _xml = xml;

        // load some other stuff
    }

    public override Chapter GetNextChapter(string chapterId)
    {
        // load chapter from xml
    }
}

Questa non è la soluzione esatta. Ad esempio, puoi caricare cose nel repository e poi passarle in oggetti concreti anziché farlo nel costruttore. Inoltre, sembra che non sia possibile avere un IBookRepository generico, poiché repository diversi caricheranno i libri in base a parametri diversi (ad esempio DB ID e percorso a xml).

Inoltre, se si desidera avere una logica di "chapter browsing", l'estrazione di tale oggetto nel proprio oggetto potrebbe essere una buona idea. Simile a ciò che gli iteratori fanno.

    
risposta data 12.11.2015 - 09:14
fonte

Leggi altre domande sui tag