Introduction
Have you ever heard of the FizzBuzz refactoring challenge? I encountered it through an article on Medium. The challenge is ...
For further actions, you may consider blocking this person and/or reporting abuse
I'm not familiar with PHP, but this seems less than ideal. It broke my heart to see you manually combinate the predicates in
FizzBuzzRule
,FizzBazzRule
,BuzzBazzRule
, andFizzBuzzBazzRule
.Are you familiar with decorator pattern? I think it expresses this sort of use case pretty elegantly. I took a few minutes to jot down an alternative implementation to demonstrate.
This snippet has the expected output of
Using a decorator this way has a few advantages:
11
printbar
) does not require defining a new class. The implementation you suggest seems to require defining a combinatorially increasing number classes to accommodate a new check.That's awesome. I love this approach too. You can literally add a new requirement without writing a new class. Very elegant code but seems confused at first glance with the boolean flags been added. Maybe you can throw more light on the flag, why they exist?
Sure Thing!
Let's consider a call to
FizzBuzzBazzRule
to see what that boolean flag is doing.We begin by calling to the public facing
eval
method defined in theRule
abstract class.This method forwards the argument to the protected
apply
method, but also passes a second boolean parameterFalse
.This boolean parameter is used to indicate whether any previous rules have successfully matched against the
$number
argument.Because
eval
is called by the client, no previous rules have run and consequently, the$number
has not yet matched against any previous rule.Note that the
apply
method is marked asabstract
. That means that we'll need to determine what method to call by considering the child class of which the object is an instance. In this case, the child class is aDecorator
with$name == 'Fizz'
and$number == 3
. So let's consider the apply method of theDecorator
.We check whether the
$number
argument is divisible by the value of the$this->number
member variable.5
is not divisible by3
so we take theelse
branch of the conditional. We pass forward the$number
argument to the next rule along with the$matched
boolean indicating whether any previous rule (which is stillFalse
).The next rule is a
Decorator
with$name == 'Buzz'
and$number == 5
.5
is divisible by5
so we take the first branch of the conditional. Again we pass forward the$number
argument to the next rule, but this time, because we've found a match, we passTrue
as the boolean argument.The next rule is a
Decorator
with$name == 'Bazz'
and$number == 7
.5
is not divisible by7
so again we take theelse
branch of the conditional. We pass forward the$number
argument to the next rule along with the$matched
boolean indicating whether any previous rule (which is nowTrue
).The last rule is a
Bottom
. This class serves as a base case for this sort of object-oriented recursion. Let's consider it'sapply
method.If no previous rule has matched the argument
$number
(as indicated by the$matched
argument), we need to print the value of$number
to the console. In either case, we print an end of line character to console to complete an iteration of thefizzbuzz
loop.Thanks for the explanation. Very impressive. I checked your profile to get your Twitter account but it seems you are not on Twitter yet. I got to follow you so we can engage in discussions in the future.
twitter.com/RakishRhenoplos
Happy to chat anytime. =)
Oop also leads to a big files/instances spread. So if yiu return to your code in a year, I guess the first "ugly" one file code will take a couple minutes to get you an idea what's going on. But classes will take much more time to track all dependencies across files. So not every solution needs oop in it of course
This is actually something we’ve discussed heavily at places where I’ve worked. Usually implementation like this makes it harder to read (which is interesting considering the arguments for SOLID are maintainability). I’ve seen these patterns beneficial in libraries to be consumed and projects that are a massive scale where design principles help with on-boarding. For smaller things, seems like overkill.
The easier solution is in the article he mentioned: itnext.io/fizzbuzzbazz-how-to-answ... (String Concatenation).
Oh yah this isn’t a dig at the authors post at all. Much can be gleaned from applying design principles to a simple problem for education sake. I was commenting on design patterns in general. They are another tool in a developers toolbox, nothing more. The article did a great job of highlighting one of the SOLID principles.
Thanks for the feedback. But I don't think I will write the code this way in production. Instead of putting this in one file. Each class will have its own file.
Though these classes don't have dependencies even if they have, tracking them is easier in modern editors/IDEs.
Based on the feedback I think you might go with a functional approach which is also a good tradeoff.
In our production, we do not choose only one approach. As I mentioned, every method has its pros and cons. And OOP approach for fast/draft release is a tortoise. For this purpose, we, of course, use functions.
Programming is about learning and using instruments for their purpose not to choose only one tool to rule them all :)
And of course, refactoring is a big drawback lately, but it is an excellent teacher. You will never write perfectly from the first time, but you should learn how to write the first time to get your app working and don't spend on it a year
At my end, it is mostly OOP. I started with procedural then later joined the OOP approach. I would love to see how different approaches are combined in one project. In your free time, you should enlighten me about that. Let's talk.
I quit PHP dev three years ago, last stuff we made was a super simple MVC framework to build a simple protected stacks [auth/reg/acl/db/pages].
So OOP is used for:
And most of the work is done by functions:
So an example of prototyping. This project, of course, is a mess for OOP programmer, everything can be refactored to OOP if needed and gain more complexity.
So one of these panels needed in automatically logging everything, that happens under the hood. So in just 10 minutes, my colleague wrote a solution directly in
init
function, that grabs data from the controller and write everything in file. He could create a Log interface, think of logging not only in a file, extend it and create FileLogger. Create some hook for middleware ininit
app, extend some base middleware model and then log there and spent on it from 1 hour to 2 days, because in that way some code could not work correctly and some part of the engine must be rewritten.Now, when this thing is working for a year, they want to connect all the panels to Grafana, so they refactored code and did this part in OOP and can send data anywhere they want.
OOP is excellent and solves many problems, but when the language allows you to save your time, write faster and more productive, why not using it?
And you know, this OOP-function skeleton of 2MB still serves its purpose and allows to create fast panels.
I like this article for what you are trying to do.
Those principles are tricky to teach/show/learn because you always run into the problem of "How am i going to show this". What often happens is either:
Oliver mentions that we should "try as much as possible to identify some patterns in our code when solving new requirements and find out what principles could be useful in improving the codebase".
The main principle I follow is KISS => Keep it Stupid Simple. Following that principle, I always go for String Concatenation for the FizzBuzz problem. See: itnext.io/fizzbuzzbazz-how-to-answ...
In real life, you gain some experience and can generally predict in what direction the software might go in the future.
Given FizzBuzz, I'd guess that business asked me to handle divisibility by 3 and divisibility by 5, so it's just a matter of time until they want divisibility by 7. However, it's not very probable they will want to add a case for when a number is a square.
That would lead my to a solution based on the general principle of prime factors, and not the special rules, so basically string concatenation.
If business wants squares, I'll say "well, that's very different from the things we had until now, so this will take some time".
Sure but as the requirement keeps changing, I will start identifying patterns and how best to prepare for future changes without changing the code too much.
Nice explanation 👌
The rules should be in reverse order, otherwise the combined rules won't be evaluated.
Awesome thanks for the input
Thanks for enlightening us on the idea of using SOLID to solve problems. But for this principles work best for larger projects. But it's great to know how to implement it.
Welcome chief. #WeRockTogether