« The Other $11,000 – What Happens When Launch Day Is Over? | Really, Always Return Something » |
Abstraction is one of the subjects in software development that’s really difficult to fully grasp without practice. It’s easy to read about it, talk about it, and profess doing it. To actually do it is another thing entirely: it requires practice, and patience, and experimentation.
I decided the best way to show abstraction in action is by showing the commits I made as I worked to abstract a model that contained the validation and database logic inside the same class. You can see my progress, as well as my notes about what I’m doing as I go along below.
This post contains full length code examples. I have used GitHub to provide them since GitHub’s diff viewer is second to none. Click on the smaller images to see the full code samples in new windows.
The biggest clue I find to indicate that further abstraction is necessary in an application is a violation of the single responsibility principle. The single responsibility principle states that every class should have one job and only one job.
When I come across a class that has more than one job, I know it can stand to be abstracted.
Breaking up classes can be challenging, especially in terms of knowing that we don’t want to break the existing functionality.
My solution for this was to leave the original model class intact as a gateway class, and offer two additional classes: one responsible for the validation, and one responsible for the storage of the data. This separates out the different jobs into separate classes, which meets the single responsibility principle test.
Even though the gateway class interacts with these two other classes, it too has only one job: message passing between different layers.
Even though I had abstracted out the data access and the validation from the gateway object, there were still a few things I could do to further abstract the objects.
Data access is the big obvious one. In fact, data access that relies on a particular data storage engine is tight coupling in the worst way. It makes it near impossible to swap one data source for another.
However, we can make some basic assumptions about how we’re going to access data. Even a NoSQL database will have the basic four CRUD actions – create, retrieve, update and delete. So let’s implement a class that would be capable of implementing these four actions (I only implemented two, since that was all that was required for this demo).
Since it’s possible I may want to initiate a certain different type of data store in the future, I want to have some sort of interface I can use for these data storage classes. The creation and use of these interfaces is known as “designing by contract”, and it’s a well-understood principle that lends to reuse and abstraction.
I defined an interface that could be used to implement a similar data storage class, one that uses Postgres, or MongoDB or even Redis.
Of course, this app is hardly testable. And even with interfaces and our design by contract strategy, we have no way of injecting the objects we want or need into the system. So let’s fix that.
We should inject all the objects we need into the ClientModel class. The ClientModel will continue to take its previously expected arguments, and will typehint against the class types it knows about. The other classes instantiate objects in their constructor methods too. Instead, we will accept those objects as arguments, and typehint on the class or interface we expect.
There are still improvements we could make, but that I did not make for the sake of this demo. The class naming strategy is not in compliance with the PSR standards. The names I selected were not descriptive enough in some instances. There’s a file system organization challenge and I’m using require statements.
The validation logic and the database logic could be upgraded to be more robust and catch different types of exceptions, and handle them differently. The validation function and SQL construction could be broken into their own, testable functions. There’s always more to do, but there’s also plenty here to demonstrate the process of abstracting a model into separate classes.
Starting on April 14th, I’ll be offering a class called The Object Oriented PHP Masterclass. One of the many topics we’ll be covering is abstraction. If you struggle with abstraction or other object oriented programming principles, this class is for you.
The Object Oriented PHP Masterclass is designed to teach you the principles of object oriented programming in a way you’ve never experienced before. The class is entirely online in the comfort of your home or office. And you’ll get a chance to get your hands dirty with actual code, and receive reviews and tips from yours truly on how to make your code better. Plus, with two live sessions, you’ll have plenty of chances to ask questions and get answers to your most pressing object oriented programming challenges. Are you ready to take your career to the next level?
The good news is there are a few seats left. You can still get a seat to The Object Oriented PHP Masterclass!
The bad news is that these seats will go fast. Plus, early bird pricing ends April 1st That’s only a week away! The class is only $499 for two weeks of personal, online hands-on instruction, but the price goes up to $599 next Monday!
So check out a copy of the syllabus, and then book your seat today. You’ll truly regret it if you miss out on this class!
Brandon Savage is the author of Mastering Object Oriented PHP and Practical Design Patterns in PHP
Posted on 3/25/2013 at 8:30 am
Ayo Akinyemi (@sayhello2ay) wrote at 3/25/2013 8:41 am:
When does several object injection become a smell ?
Brandon Savage (@brandonsavage) wrote at 3/25/2013 8:46 am:
I would say that it becomes a smell when it exceeds a reasonable threshold. What that threshold is depends on the context and experience, plus the intent of the code. Injecting two objects is not a big deal, but injecting 12 would be, especially for this small a bit of code.
Jurian Sluiman (@juriansluiman) wrote at 3/25/2013 9:30 am:
It is always a hard task to abstract classes and refactor your code base. It is especially a hard job if you try to refactor based on your own gut feeling: when does a class perform more than one job? How do you avoid Javafication? What methods are required for the interface, what is a concrete implementation? What is the limit for dependencies injected?
I mainly get help for these questions by applying software patterns. From PEAA, from the GoF or any other similar resource, that doesn’t really matter. They all provide very good abstract patterns applied to concrete problems and help you think **how** to split or refactor classes so it would make more sense.
Also, I would not apply first the Extract Composite refactoring but rather define interfaces first (based on the patterns you know). With the interface graph in place, it is much easier to refactor the classes to your “ideal” situation.
And as last, PSR-0 compliance and stuff should be taken into account directly! It is not much work and refactor those at a later stage cost significant more time than directly applying the standards. For PSR-1 and PSR-2 you can use tools, but PSR-0 might affect your namespace scheme as well, that will work through your complete code base. It’s much better to start directly the right way. And if you are refactoring an existing project (so you don’t have made the “initial commit”) then this PSR-0 compliance should be the first step you do. It allows others to work on the project simultaneously afterwards. If you do the PSR-0 compliance in the end, you create a mess for everyone.
PS. I haven’t looked at the code behind your commits, I wrote this just based on the text in this article.
Brandon Savage (@brandonsavage) wrote at 3/25/2013 9:39 am:
I find that for less experienced developers, asking them to examine the big picture up front often presents challenges that they struggle to deal with, especially when faced with existing code. Instead, working backwards and showing them the eventual outcome can help them work forward on refactoring the code they have.
Most developers don’t have the opportunity to write clean, clear, new code. Most have to deal with legacy code. Obviously to create an example I needed to have the “initial commit” but my strategy would not have changed if I was demonstrating an existing model.
As for the PSR’s, most existing code that developers will encounter doesn’t follow them. They’re certainly useful, but they are not uncontrovertial, and they are also not widely adopted by more than just the frameworks at this point.
« The Other $11,000 – What Happens When Launch Day Is Over? | Really, Always Return Something » |