views:

45

answers:

4

To track revisions of a Page class, I have a PageRevision class which inherits from Page and adds a revision ID (Guid RevisionID;).

If possible, how should I cast an existing Page object to a PageRevision and ensure that the PageRevision constructor is called to create a new revision ID?

I could could have a PageRevision(Page page) constructor which generates the Guid and copies all the Page attributes, but I want to automate it, especially if a Page class has many attributes (and I later add one, and forget to modify the copy constructor).

Desired use

Page page = new Page(123, "Page Title", "Page Body"); // where 123 is page ID
PageRevision revision = (PageRevision)page;
// now revision.RevisionID should be a new Guid.

Page, PageRevision classes:

public class Page
{
    public int ID { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
}

public class PageRevision : Page
{
    public Guid RevisionID { get; set; }

    public PageRevision()
    {
        this.RevisionID = Guid.NewGuid();
    }
}

Edit based on feedback:

Besides the now-obvious (Horse)Animal; casting problem, Jon Skeet recommends a composite revision:

public class PageRevision : Page
{
    private readonly Page page;
    private readonly Guid id;
    public Guid RevisionID { get { return id; } }
    public Page Page { get { return page; } }

    public PageRevision(Page page)
    {
        this.id = Guid.NewGuid();
        this.page = page;
    }
}

However, this is quite different from my data model and I'd like to keep the two as similar as possible. In my database, the PageRevisions table has the same columns as the Pages table, expect for an extra RevisionID column. This is easy to version with a database trigger.

  • In the light of this composite approach, would it make more sense to have a PageRevisions to store all page data: a RevisionID, Title and Body, while a Pages table only stores an URL Slug and a RevisionID that refers to the PageRevisions table?
+3  A: 

Why not make your PageRevision class compose instead of inheriting?

public class PageRevision : Page
{
    private readonly Page page;
    private readonly Guid id;
    public Guid RevisionID { get { return id; } }
    public Page Page { get { return page; } }

    public PageRevision(Page page)
    {
        this.id = Guid.NewGuid();
        this.page = page;
    }
}
Jon Skeet
Good idea. I was trying to keep my domain model as close as possible to my data model, in which I have a `PageRevisions` table with the same columns as my `Pages` table + a `RevisionID`. But, your answer has made me consider a central `PageRevisions` table and a `Pages` table that merely references the *current* Revision for a given PageId and page slug. I'll look into revisions some more...
FreshCode
In this case making the Object model look exactly like the data model needs to be for sql, may not be the way to go. Do it how its right in OO and map it to the data. Don't compromise your api and programming model in the business layer because you chose a distinct backing store.
DevelopingChris
+1 @DevelopingChris for keeping your business layer clean. I'll post another question on the best revision pattern for pages like this. Will also have a look at how SO does it.
FreshCode
A: 

During a cast, there is no constructor called, because the object is already created.

Although your cast will fail at runtime, cause Page cannot be cast to PageRevision (the other way is possible)

In your case i would add the RevisionId to your base class Page. If you create a Page object it could be created with Guid.Empty. Derived classes could set the RevisionId using an constructor of your base class Page.

public class Page {

    public Page() {
        RevisionId = Guid.Empty;
    }

    protected Page(Guid revisionId) {
        RevisionId = revisionId;
    }

    public Guid RevisionId {
        get;
        private set;
    }
}

public class PageRevision : Page {

    public PageRevision()
        : base(Guid.NewGuid()) {

    }
}
Jehof
+1  A: 

You cannot.

A horse is an animal, but not every animal is a horse.

So horse => animal is possible, but animal => horse not. And you are trying to cast your animal into a horse.

Jan Jongboom
+1 good analogy. Yeah, I thought that might be a problem.
FreshCode
+1  A: 

The PageRevision constructor ALWAYS gets called regardless if you cast the class to PageRevision or not. So this isn't going to work at all.

It likely makes more sense you tell why you want to do that because you are likely doing that for reasons that are solved in other ways.

Foxfire