C# – Empty virtual method on base class VS abstract methods

abstractioncdesignobject-orientedobject-oriented-design

I couldn't find a question that was not too specific to some case, so I'll try to make this very generic.

We need an extractor base class to a set of documents, for example. Each document has its specific properties, but they're ultimately documents. So we want to provide common extraction operations for all of them.

Even though they're all documents, as I said, they're somewhat different. Some may have some properties, but some may not.

Let's imagine that we have the Document base abstract class, and the FancyDocument and NotSoFancyDocument classes that inherit from it.
The FancyDocument has a SectionA, the NotSoFancyDocument doesn't.

That said, what would you defend as the best way of implementing this? Here's the two options:

  • Empty virtual methods on the base class

Empty virtual methods on the base class would allow the programmer to only override the methods that make sense for the different types of documents. We would then have a default behavior on the abstract base class, which would be returning the default for the methods, like this:

public abstract class Document
{
    public virtual SectionA GetDocumentSectionA()
    {
        return default(SectionA);
    }
}

public class FancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        // Specific implementation            
    }
}

public class NotSoFancyDocument : Document
{
    // Does not implement method GetDocumentSectionA because it doesn't have a SectionA
}
  • Concrete empty methods or concrete methods throwing a NotImplementedException

Since the NotSoFancyDocument does not have a SectionA, but the others do, we could either just return the default for the method in it, or we could throw a NotImplementedException. That would depend on how the program was written and some other things. We could come up with something like this:

//// Return the default value

public abstract class Document
{
    public abstract SectionA GetDocumentSectionA();
}

public class FancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        // Specific implementation
    }
}

public class NotSoFancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        return default(SectionA);
    }
}

OR

//// Throw an exception

public abstract class Document
{
    public abstract SectionA GetDocumentSectionA();
}

public class FancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        // Specific implementation
    }
}

public class NotSoFancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        throw new NotImplementedException("NotSoFancyDocument does not have a section A");
    }
}

Personally, I do think that the abstract method approach is better, since it means "Hey, I need you to be able to get a SectionA to be a document. I don't care how." while the virtual method means "Hey, I do have this SectionA here. If it's not good enough for you, feel free to change the way I get it.".

I do think the first one is a sign of object oriented programming smell.

What are your opinions on this?

Best Answer

In this case the base class should know nothing of SectionA. The derived class should implement the extra properties that that type needs.

For certain operations where another class needs to pull information out regardless of the type of document you will want that method on the base class ideally virtual with a basic implementation and allow derived classes to override it if needed (eg ToPlainText which would just output all the sections of a document would be on Document, FancyDocument can override the implementation to also output SectionA).

For instances where another class doesn't care about the type of document but does care if it has certain properties use interfaces. IDocument would have all the common sections and Document would implement it. IDocumentWithSectionA would inheerit IDocument and FancyDocument would implement that. This then lets you derive another NeitherFancyNorNotFancyDocument that has SectionA which can also implement IDocumentsWithSectionA.

Obviously You'd have more useful names than IDocumentWithSectionA but that depends on the usecase.

TL;DR use the abstract class for what should be common to all documents and some common functionality, use interfaces as the contract saying what a document has.