4 ms·
I wonder why so many books and articles like this one are written as though the approach they're championing is the only right approach, or always the right app
by thaumaturgy 7y ago
I wonder why so many books and articles like this one are written as though the approach they're championing is the only right approach, or always the right approach for every situation. There's never any consideration like, "in the following examples, this might be a good tactic..."
Looking at the subject for this post:
Inheritance is a good, straightforward approach when you have multiple related classes which share a lot of code but don't interact directly with other unrelated classes:
class document
{
public function title ()
{
}
public function get_plain_text ()
{
}
}
class word_document extends document
{
public function get_plain_text ()
{
}
}
In a document management application, you might need to represent lots of different kinds of document formats, each with their own specializations, but also with a lot of common attributes too. These document classes might not need to interact directly with a database or a network socket, so a straightforward and traditional class hierarchy would be the natural choice here.
But then let's say you wanted to build an abstract data pipe. A data pipe needs to talk to lots of different things; maybe it's a plain old network socket, maybe it's a database engine, maybe it's an API. Here, you get no advantages from using inheritance, because although each data pipe is trying to mimic the behaviors of any other data pipe, under the hood their code is completely dissimilar. You'd end up having to rewrite so much of the functionality that there'd be nothing really for a base class to actually do.
This is where composability and interfaces are the right approach:
class phabricator_datapipe implements datapipe
{
private $_api_client;
public function __construct ($api_client)
{
$this->_api_client = $api_client;
}
public function search ($parameters)
{
// Build a search query out of $parameters specific to
// this API, then:
$this->_api_client->search(...);
}
}
Each datapipe class strictly follows a common interface structure. Dependency injection is used during instantiation to compose the runtime object, so the responsibilities in the datapipe class are limited to translating the application's business needs into the specific structures required by the API, while the API class takes care of authentication and error handling.
There is room for both of these approaches in the same application, because they solve entirely different problems. The inheritance of the documents classes solves the problem of dealing with similar-but-different units of data in a maintainable, extensible way. The composable interface of the datapipe classes solves the problem of interacting with completely different external components in similar ways.
This likewise is where the post falls over, IMO. It presents an argument for composition, but is too lazy to provide any of the context that really makes composition look like the right answer. Whether a Car object should be modeled as a child of a Vehicle base class, or whether it should be more of a standalone class with other objects injected into it, depends entirely on whether the application just wants to say:
Vehicle->GetTimeToDestination(...)
or whether the application instead needs to:
Vehicle->StartYourEngine(...)
(Aside: I think a major failing of posts and books on programming is using contrived, made-up examples, especially when talking about abstractions like object-oriented programming. Those don't help anybody understand the advantages and disadvantages of different approaches. I'm using some examples from applications I've written.)
- mobjack 7y agoInheritance usually looks more natural when it comes to toy examples. It the real world, a few edge cases can break down an elegant inheritance hierarchy and composition is often the better solution.