问题
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?
回答1:
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.
回答2:
I wouldn't choose any of those options.
Both options would violate the Liskov Substitution Principle, which states that if B
is a subtype of A
, then any instance of A
can be substituted with an instance of B
.
Using either an abstract method that throws an exception or a virtual method that returns null would break the expectation that every Document
is able to return a valid SectionA
when GetDocumentSectionA
is called.
You said it yourself, not all documents have sections. So why would Document
have a method to retrieve a section?
来源:https://stackoverflow.com/questions/22256157/empty-virtual-method-on-base-class-vs-abstract-methods