Solid OOP design part IV

In the previous lesson we introduced interfaces and talked about how they should be used. In this lesson, we will expound upon that a bit more by explaining the Integregation Segregation Principle (I.S.P.). Here's a link to this lesson on github: https://github.com/cegrif01/menu_maker/tree/integration-segregation-anti-pattern

We know that interfaces can be used to force behavior. Did you know that you can sprinkle many different behaviors on the same object? You can do this with traits and interfaces. However, for today's lesson we're going to focus on interfaces. Let's take a look at the feature for this lesson.

Updated HtmlExporter

In the previous lesson, the Html exporter outputs a string value. Let's modify this instead to place a file containing the html for the table we want to assemble.

HtmlExporter.php with export method

HtmlExporter::getMessage

Now when you export an html file, it will place the raw html of a table containing the menu in a given directory. I know that I did some crazy looping and such to assemble the getMessage() method. This isn't the focal point of the lesson, but if you're curious you can look at my use of array_key and array_values to see how I use them.

The Feature:

Now that we have added more intelligent functionality to the HtmlExporter, let's add a feature.

If the user chooses the html option, we will send an email as well as render the html view. However, if the user chooses the csv option, there will be NO email sent. This may seem a little silly, but I believe it will really demonstrate the details of the integration segregation principle.

The Anti-pattern

Let's take a look at how one might perform this. Keep in mind that the CsvExporter does not have the ability to email while the HtmlExporter does.

We will start by adding an additional email() method to the Exportable.php file.

Exportable interface with email

Let's modify the HtmlExporter file a bit. To accommodate email. I added some logic to send an html table to a specific email address.

HtmlExporter with email

GOTCHA
Here's where we are going to violate the integration segregation principle!

Since the Exportable interface has an email() method, it is required that HtmlExporter have an email method as well. However, the CsvExporter class has to implement that email() method too. The problem is, the CsvExporter class has no need to use it. So look at how ridiculous the CsvExporter class is going to look:

CsvExporter with blank email method

As you should notice, the email method is empty. The root cause is due to the fact that we are trying to force a class to have behavior that it shouldn't have. We have established that CsvExporters shouldn't have email capabilities.

Integration Segregation Principle in a nutshell

It is better to have a class implement several interfaces with different behaviors than to have a class implement one interface with several different behaviors.

Because, we tried to force email behavior on something that should only be responsible for exporting, we end up with something weird, like a blank method, in the CsvExporter class. Let's do this the correct way.

Applying the I.S.P.

First, let's remove the email method from the Exportable interface.

Exportable.php with email removed

Next we will create another interface called Emailable.php and add the email method to that interface.

Emailable.php with email method

You do NOT have to do this, but I really like to organize my apps as I write them. It makes readibility much easier. I'm going to move both interfaces in their now directory called Contracts.

If you decide to this, here is what your directory structure should look like:

directory structure of menu_maker

Also, don't forget to require the correct files relative to our new Contracts directory.

Now that we are using the I.S.P., we need to remove the useless email method from CsvExporter.

Note that now all we have is he export method on the CsvExporter class.

CsvExporter with email removed

In the HtmlExporter class, we will just make a simple change by adding a second interface and requiring the contract (or interface).

HtmlExporter implementing both export and email

HtmlExporter email method

Now you see how to implement multiple interfaces and apply the I.S.P. Pretty cool, huh?

The MenuMaker Client

Now we must update our Menu Maker class. There are a number of ways to do this. The way I'm going to show you may not be the most ideal way, but it will definitely get the job done.

updated MenuMaker class

There are very few times where using the instanceof operator is appropriate. In our case, we have an exporter that also implemements an Emailable interface. Rather than breaking up the logic too much it's better to just add an instance check for the exporter that also contains behavior to email. Not all classes that implement the Exportable interface will implement the Emailable interface. However, if it happens to satisfy both contracts, the email method will be called on that particular object.

Next Time

In the next lesson, we will look at the Dependency Inversion Principle. There are quite a few code smells left that need to be worked out. Throughout the lesson, we've seen things such as instantiation inside a class method. Also we see $_POST being used inside most of the classes. Furthermore, we see hard coded string values in the export methods. If you don't understand why these are bad, then you're in for a real treat in the next lesson. Stay tuned.