Solid OOP design Part II

In this lesson I'm going to throw a barrage of terms at you. I will cover the Open/Closed Principle and the briefly go over the factory design pattern.

If you want to view the source code for this lesson, check it out here:
https://github.com/cegrif01/menu_maker/tree/part-two-open-close-fix

Continuing where we left off, our MenuMaker looked like the following:

Last part of part 1

This is a huge improvement over what it looked like in Part I. However, there are still a few problems with this approach. What if we decide to use a different Validator class? Or even the CsvExporter? As discussed in the previous part of this lesson, we eventually want to give the user different options for exporting their menu data. With the way we have this, it is impossible to dynamically accommodate the addition of exporting options.

The Feature

Now that your wheels are turning, let's jump right into the summary of this lesson. Let's add another implementation of exporting data. Let's give the user the option of choosing what format they would like to have their menu. Depending on that user input, they will either get their menu in html or csv format. If the user doesn't choose an option, let's just be nice and export a csv as the default option.

Let's modify our view to accommodate this. Keep in mind that in a real world application, you will want to make something that is a little better designed.

html view with check boxes

Our array structure will look like the following when we var_dump the $_POST array after a form submission:

new array structure with checkboxes

We can now have two different options for exporting. Let's modify the code to accommodate. First I will purposely program the anti-pattern to the Open/Closed Principle and the Dependency Inversion Principle. I believe very firmly that the only reason why good code exists is because there is bad code and vise-versa. Ponder on that for a second!

The Anti-pattern

the anti-pattern
This code does exactly what the requirements state. We had to create an HtmlExporter class to output the html file. For now the Html file just returns a string. We aren't really that concerned what the HtmlExporter does right now, as long as it has functionality.

HtmlExporter.php

Html exporter

What's wrong with this?

Well, for one, every time we want to add or modify an Exporter, we have to touch the MenuMaker class and the exporter class. In the other words the MenuMaker and the exporter objects are what you would call “tightly coupled.” This not only violates the Single Responsibility, but it also violates the Open/Closed Principle.

What is the Open/Closed Principle?

The formal definition states that Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification. In other words, An entity can allow its behavior to be modified without altering its source code.

I find that pure definitions rarely bring understanding, so let's view this principle in the context of the Exporters.

The Open/Closed Principle Refactor:

We will move the logic of figuring out which exporters to instantiate outside of the MenuMaker class.

MenuMaker.php
refactored MenuMaker class

ExporterFactory.php
exporter factory refactor

What is a factory?

A factory is a design pattern that hides the implementation of how objects are created. I want to create different objects that have different behaviors depending on user input. In our feature section, we are told that if there is no input then run a csv export. Otherwise, choose either an html, csv, or both depending on the checkboxes from the user. Just describing this process, makes it obvious that there will be conditionals. Using the Exporter Factory, we can decide what objects are needed at runtime. Then in the MenuMaker class, all we have to do is loop through and call export on each object.

We already know the exporter factory will send back an array. We also know that both the CsvExporter and HtmlExporter classes have an export method. Knowing these two things, we can loop through these objects and call export on each one. The modified code, is now open for extension and closed for modification. I can add more Exporter classes for the ExporterFactory to churn through. Perhaps, I can add a PdfExporter object and the MenuMaker class won't care.

So Now What?

We have come a long way, but we still have a few problems. What happens if later down the line another developer working on this project decides to create another Exporter but name the method exportStuff() instead of export()? How do we guarantee that all Exporters have the export() method on them? Well, stay tuned. On the next lesson, we will talk about the Liskov Substitution Principle, Type hinting, interfaces, and polymorphism.