Software Engineering: the two most useful Object-oriented principles

E Ciotti
E Ciotti
Oct 13, 2019 · 3 min read

Apart from simple scripts and other minor cases, designing software is difficult. Projects evolve and change depending on frequently changing business needs. The software modules inevitably change, get replaced, get extended or removed and it’s important to design them accordingly. I’ll illustrate some of the SOLID principles, along with a code example, explaining how they help in designing more maintainable code.

One component should only do one thing

Having one component (a class in OOP terms) doing more than one thing is a risk. One of those two things will likely be needed elsewhere, obliging to rewrite or duplicate the functionality. This is basically the idea behind the Single responsibility principle:

A class should only have a single responsibility, that is, only changes to one part of the software’s specification should be able to affect the specification of the class.

Look at the class doing the two different things:

  • Downloads a file from the network with the currency exchange
  • Parse it and returns the EUR/USD daily exchange rate
class CurrencyConverter
{
public function getEuroExchangeRates(): array
{
$html = file_get_contents('http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml');
$root = simplexml_load_string($html);
$ret = [];
foreach ($root->Cube->Cube->Cube as $node) {
$ret[(string)$node['currency']] = (string)$node['rate'];
}
return $ret;
}
}
$c = new CurrencyConverter();
echo 'Eur to USD exchange rate: ' . $c->getEuroExchangeRates()['USD'];

Do you see the problems with this?

  • The method is hardcoded to use the network to get the file before parsing it. You can’t write a unit test to assert how the XML parsing logic works with different inputs. You could move the URL to a class field, but then it’ll still be tricky to mock the network to return the XML file. It’s a code smell that your class does too much and needs splitting;
  • It’s difficult to read one long method doing more than one thing. Simple and well-named methods are vital to make your code understandable by other developers (and by yourself too when you look at your old code).

Functionalities should be replaceable

Coded functionalities should be replaceable, without changing the rest of the code. In OOP terms, that would mean defining a functionality (method) within an interface and implementing that interface in one or more classes.

Following the same example mentioned above, the “file downloading” is a functionality, and it should be defined by an interface. Example in PHP 7 code:

interface DownloaderInterface
{
public function download(string $url): string;
}

This download functionality can now be implemented in many different ways, e.g. using a native function or other libraries

class PHPNativeDownloader implements DownloaderInterface
{
public function download(string $url): string
{
return file_get_contents($url);
}
}
class CurlProxied downloading implements DownloaderInterface
{
public function download(string $url): string
{
/// download using CURL and a proxy
}
}

This functionality can now be used by other components. Have a look at this example: a class that uses the downloading functionality, by declaring the interface as argument, and using its method.

class MainApp
{
/*** @var DownloaderInterface */
private $downloader;
/**
* App constructor.
*
@param DownloaderInterface $downloader
*/
public function __construct(DownloaderInterface $downloader)
{
$this->downloader = $downloader;
}
public function doSomething()
{
$this->downloader->download('http://site.com/file1.html');
$this->downloader->download('http://site.com/file2.html');
// ...
}
}

You can now create one downloader and inject it into the MainApp class:

$downloader = new PHPNativeDownloader();
$app = new MainApp();
$app->doSomething();

The huge advantage here is that you can replace the download functionality (e.g. using the other class CurlProxied), without touching the MainApp. This code is ways better as you can easily replace the functionality.

In real projects, the downloading will be used by many other classes, and you’ll likely use a Dependency injection container that allows defining just once the downloader object. If your app has 100 object using the downloader, you can change its declaration in just once place; the dependency injector will inject it into all the classes requiring that interface. And all those classes will be happy, as the interface contract is respected, nothing else to change.

Two SOLID principles related to this are the Open–closed principle:

Software entities … should be open for extension, but closed for modification.

and the Dependency inversion principle:

One should “depend upon abstractions, [not] concretions.

Thanks for reading!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade