2

I have a simple object like this

  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 this domain (actually anywhere in the world!), book has chapters and book has ToC. In our application, there would be two possible implementations of repository i.e. the book object may either be loaded from Xml or be loaded from database. These books are pretty large in size i.e. two-four hundred thousand chapters (This is all about book metadata, content is separate but out of question for now)

My problem is that when the book is loaded from database, I want to leverage database capabilities of querying and indexes etc. For example, in the GetNextChapter, 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. As you can see, these two implementations by nature are different, but at any given time we will be using only one of those. I am not able come up with a uniform interface of operations that both these implementations can follow. Let's say if I decide to have operation GetNextChapter implemented in both, then for database repository, I just need the chapter ID, whereas in the Xml repository, I need the whole book object or at least list of chapters (and even that has nothing to do with xml actually because that's just in memory filter).

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?

EDIT To clarify further, if I add GetNextChapter method in the interface, Database implementation needs only the chapterID as parameter, whereas Xml implementation would need the book object. Similarly 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).

So how will these two go against a single interface?

6
  • If you're going to code to a single interface, then both implementations should adhere to the same contract and return the same kind of data. Not to make things more complicated, but an interface is more than an interface. It's also the pre-conditions, post-conditions, and expected return values. In other words, if your XML implementation doesn't return the same data, clients will likely need to type check because you've broken Liskov's substitution principle.
    – RubberDuck
    Jul 7, 2016 at 0:12
  • Could you format the question a little bit. Now it's a wall of text. Adding some bullets, splitting some paragraphs or adding some section titles would make easier to understand, Jul 11, 2016 at 13:10
  • I suppose it depends in part on how you identify the type of source (db or XML). Do you try to access it from both and then read it from whichever is found first? Do you try in sequence to access from one and then the other? I suggest you have one "book" abstraction layer and you have the two implementation for getting the book from db or XML file(s) and then a bit of glue between the lower-level and the "book" object layer.
    – user236030
    Jul 12, 2016 at 3:47
  • @RubberDuck thanks for the directions, I could see what I was doing wrong. Expecting two different implementation to be consumed by client without having knowledge of their internals, which as you said, was indeed wrong, but I did not realize that. Some answers below gave me together a good view about approaching this, but your comment was indeed helpful. Jul 13, 2016 at 13:15
  • @MarkH it's basically contextual, and both these modes are mutually exclusive. To give you more details, these are consumed by different type of clients one is a background process and one is active web application. Background process client does not worry about time it takes, so long as it's done, so that's where I am loading it from xml one time & cache thereafter, whereas web application needs data on the fly, so the same data is extracted (one time) from xml and stored in database, which is used by web application client. Jul 13, 2016 at 14:34

6 Answers 6

2
+50

OK I think some of the other answers touch on this, but I will spell it out.

Constructors are not part of the Interface so you can have different ones for each repo.

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

and

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

then Both repos can implement the same method

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

or

public Chapter GetChapterById(string id)
{
    var data = this.dbConnectionIWillUseLater.Execute("select * from chapter where..");
    return this.PopulateChapterFromData(data);
}
5
  • Can you expand on this a little? How is chapter-within-book identified? Or are you assuming chapter is unique among all chapters of all books? Are you suggesting that some mapping of book-to-chapter is loaded for each book?
    – Euphoric
    Jul 12, 2016 at 6:10
  • The OP says the sql only needs id, so i assume this is a unique identifier for chapter. Looking at your answer I think ive interpreted the question a bit differently to you
    – Ewan
    Jul 12, 2016 at 9:36
  • Thanks Ewan. This gives me good directions, as @RubberDuck pointed above in the comments, I see that two implementation following same interface must have same input output unless we want client to be known of their internals. Additionally as you showed above, I can keep them still separate by correct use of interface. Some other answers also were helpful for me understanding how to proceed. I still have to figure out complete picture but all of them together helped me in getting to the right direction. I will accept yours at the moment. Jul 13, 2016 at 13:20
  • Ew, opening database connection and parsing data in constructors.
    – Andy
    Jul 14, 2016 at 14:13
  • Yeah i know, but keeps example code short
    – Ewan
    Jul 14, 2016 at 15:09
2

I would decouple the three responsibilities for a repository (load book from XML, load book from a database, cache book) into three different implementations:

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);
    }
}

With these 3 implementations you can compose either a cached xml repository:

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

or a simple database repository:

new DbBookRepository("connString");

and as a bonus you can also create a cached db repository without any extra effort:

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

or if you remark that the cached xml repository is over-engineering, you can remove it in only one place without affecting anything else in your code:

new XmlBookRepository("data.xml");

Usage:

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
2
  • And he exactly says he doesn't want "load the FULL book and returns it". He wants to load only piece and then do additional querying as the book is used. Sort of lazy loading.
    – Euphoric
    Jul 12, 2016 at 6:07
  • This is a good approach in general. Worth thinking, so I will try to incorporate part of it definitely. Jul 13, 2016 at 13:22
0

If I understand correctly, your Book is basically a list of chapters with additional metadata and you want to add it two ways of loading through a unified interface. But depending of the persistance, the get/load operation changes.

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

So, simply force that you need the book object. Then implement the get with either of the data (ID or chapter list) depending of the underlying repo. A method that would get a chapter by id without book reference doesn't belong to Book class, it is a seperate Chapter constructor/loader.

Also, it is unclear to me whether or not you want to implement GetNextChapter as an iterator (current chapter is inside the class, no id parameter) or a random id accessor (I'd call this GetChapterById, with id as a parameter). But I assume it is an Iterator and does belong to 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).

Partial objects don't sound very correct to me. You have several possibilities, but what I would do is make the book repository implementation support abstracted get/load operations. BookRepository inevitably support distincts implementations depending if it is XML or DB, so it sound logical to put the abstraction here. What would happen to your book class is that ListChapter would not be a field of Book, it would be a field of your XMLBookRepository only.

-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 short, Interface Segregation principle is important because if a certain class becomes the new hotness then one would want to use it everywhere.

Client should not be forced to use a interface if it does not need it.

-2

This is a perfect example for an use of interface, or if you may, a public API. Within your Book domain, you are dependant on an implementation of IBookRepositoy, to whom you will interact with via it's public API (its public methods).

Now you just need to follow the Single Responsibility Principle and Separation of Concerns in order to achieve a good design. What you want here is to have multiple implementations of IBookRepository, that are not known to your Book domain object. You could say XMLBookRepository and DatabaseRepository would be your 2 different implementations.

That being said, the implementation used in both of this repositories should stay hidden from the Book domain, as it will interacting via the public API, the IBookRepository interface.

Once that's on the table, you need to implement both repository in the way they are needed. Note we are talking about 2 different implementations; this is because, as you yourself stated:

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.

This is a clear indication that you need multiple implementation.

1
  • 1
    I am little bit confused or I should say my question was kind of misleading. (I will correct it now). I follow you upto the part of creating interface and having two different implementations. My problem is further down i.e. when I am defining interfaces, I am not able to come up with common methods because each implementation requires different parameters. See Edits for more details Aug 13, 2015 at 16:10
-2

Simple. Make Book abstract class and implement it's loading in concrete classes.

First, I would not mix up "repository-like" methods in Book, but keep it only in 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
    }
}

This is not exact solution. For example you can load stuff in repository and then pass it into concrete objects instead of doing that in constructor. Also, it seems that you cannot have generic IBookRepository, because different repos will load books based on different parameters (eg. DB ID vs. path to xml).

Also, if you want to have "chapter browsing" logic, then extracting that into it's own object might be good idea. Similar to what iterators do.

11
  • 2
    Why should a Book know anything about a XML document or (even worse) a Database? I don't want a database, I want my Book.
    – Andy
    Dec 12, 2015 at 11:28
  • @DavidPacker And that is exactly what you get. Did you even read my answer?
    – Euphoric
    Dec 12, 2015 at 15:47
  • No, you do not. For example your DbBook knows about the repository, which it should not.
    – Andy
    Dec 12, 2015 at 16:17
  • @DavidPacker Well, good thing that is DbBook and not Book. Book knows absolutely nothing about database or XML. And code using Book has no idea either. That is true power of abstraction.
    – Euphoric
    Dec 12, 2015 at 16:41
  • 1
    You are then most likely introducing anemic models into your application, passing these models around your application and have services do all the work. If you have services to manipulate data of a class, why not put a method into the manipulated class itself to perform the business logic? Which brings us back, once your class contains business logic, it should not contain persistence logic and vice versa.
    – Andy
    Dec 30, 2015 at 9:43

Not the answer you're looking for? Browse other questions tagged or ask your own question.