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?
SectionA
? Why would the high level Document interface not specifySections
instead? When you say "all documents have a section A... oops, except this one type" that is an indication that either the one type should be separate, the document specifies too much of an interface, or maybe it is named incorrectly (not really a document).