Java Question.
The use of abstract and interface classes. I can understand the distinctions between them but can't quite fathom their uses. Why would I want to define a class that is incomplete or has partial implementation but needs to be completed later in another class?
Making a class and implement everything, making it complete makes more senses to me than the aforementioned. Because I know all I have to do is use them as I wish and not think about, completing it later.
Should I want to introduce new methods, it is not a problem, I can override or make a new class inheriting or copying another class.
Someone earlier mentioned the Strategy Pattern as perhaps a starting off point for understanding, and patterns can indeed demonstrate effective usage of abstract classes and interfaces.
As an example, as an author of a class, you might know some generalities about how the class has to operate, but you do not have the complete picture, or there are several mutually exclusive possibilities for execution. While you might consider putting an if/else or some other logical structure in your code, it can grow as requirements change and become unwieldy. Another option is to punt. Define a base abstract class, fill in as many details as you can, and provide abstract methods for subclasses to fill in the gaps as they require them. And as requirements change, the abstract base class and existing subclasses might be protected from change, provided the change is an area already identified as volatile. Leave the existing classes alone, add another subclass. Applying this technique over an algorithm, for example, is often referred to as the
Template Method Pattern.
But maybe you do not have that degree of change or volatility in your code. You might not, it might be fairly straightforward and stable, with not many repeated if/else structures or anything like that. But you can still benefit from the concept of abstract classes and, notably, interfaces. The writing of
unit tests really highlights the effective use of interfaces.
You cover your code with tests, each one trying to test as small a unit of work as you can get away with, with laser-like focus on a given piece of logic. If that test should fail, you want to know precisely what did not work as expected, without having to do a lot of debugging and stepping through code line by line. It should pop out at you, preferably immediately. However, this is often hard to do. You have a lot of classes working together (if you have generally good separation of concerns) or you might have a kitchen sink procedural code method that does everything. When you write tests for that, you find it hard to arrive at the type of isolation that you desire when and if something fails. Extracting code into classes and further extracting interfaces from those classes allows you to arrive at decoupled, testable code that will let you
replace functionality that is irrelevant to your test (or maybe could just slow your test down) so that you can focus on what is truly important in that particular test scenario.
A crude example, making it up on the fly. Say you have a program that calls out to a website to pull down an RSS feed, then parses that feed and stores it into a database. At a high level, you have 3 steps to this algorithm. 1) Get feed. 2) Parse. 3) Save to database. The details of each of these steps can be full of details. But at that high level, all you are maybe concerned about is that the algorithm executes. So what do you do? Maybe you express each step of the algorithm in its own class. Your method under test is simply responsible for managing its workflow.
Code:
public void LoadRSS()
{
var feedItems = rssReader.GetItems();
var parsedItems = rssParser.Parse(feedItems);
db.Save(parsedItems);
}
That looks pretty simple, doesn't it? But how do you test it? Do you have to find some RSS feed that exists, verify what's there, pull that down, verify all the data gets parsed correctly, and then look in the database to make sure that everything you wanted to be there is there in the correct format? That's a pretty big test. But we stated before that all we were concerned about
in this test is the execution of the algorithm. That the program read the feed, parsed the items, and saved them. The details of all those actions are irrelevant. Well, what we can do is from those classes, we extract interfaces and replace them during our tests with fakes. (And it's not that we don't care about those processes, it's just that we will write separate, isolated tests for them, as well.)
Code:
[TestMethod]
public void LoadRSS_AlgorithmExecutesSuccessfully()
{
var reader = new FakeRssReader();
var parser = new FakeRssParser();
var db = new FakeDatabase();
var stagedItems = new List<RssItem>();
var stagedParsedItems = new List<RssDataItem>();
reader.SetRssItemsToReturn(stagedItems);
parser.SetParsedItemsToReturnForRssFeedItems(stagedItems, stagedParsedItems);
db.SetExpectedDataItems(stagedParsedItems);
var loader = new RssLoader(reader, parser, db);
loader.LoadRSS();
db.Verify();
}
With the fakes in place, it's fairly simple to test the algorithm. You expect the reader to be invoked, it will return items. You expect those items to be passed to a parser that will return other items. And you finally expect those parsed items to be sent to the database. What each of those classes are doing is not the point of your test. You just want to verify the algorithm, and you have.
What makes it work is the interfaces, and then RssLoader exposing dependencies to those interfaces in the constructor.
Code:
public interface IRssReader
{
IEnumerable<RssItem> GetItems();
}
public class RssReader : IRssReader
{
public IEnumerable<RssItem> GetItems()
{
/* real details here */
}
}
class FakeRssReader : IRssReader
{
public IEnumerable<RssItem> GetItems()
{
/* fake details here */
}
// helper methods here, to assist staging data and/or setting expectations
}
public class RssLoader
{
IRssReader reader;
IRssParser parser;
IDatabase db;
public RssLoader(IRssReader reader, IRssParser parser, IDatabase db)
{
this.reader = reader;
this.parser = parser;
this.db = db;
}
Anyway, that's probably more info than you cared about, but given time, you will find patterns and testing will drive you to interfaces and abstract classes more than you realize.
(Code samples follow C# idioms, as an FYI.)