Imagine you want to have a room painted. You are the owner of this room and a Painter
is capable of painting the room. There are different types of Painters
: a BluePainter
, a RedPainter
etc. For every color there exists a Painter
! You don't care in what color the room is going to be painted, as long as the room gets painted. This is a strategy pattern situation.
(By the way, I know that it makes more sense to have different types of Colors
and give Paint
or Painter
a Color
instance, but let's use my example where every type of Painter
is only capable of painting one color.)
There are two general ways to get my room painted. I am going to illustrate both these ways in two different situations.
In the first situation, the Painter
instantiation does not require any parameters.
Option 1: you have been provided a Painter
instance whom you tell $painter->paintRoom()
. The Painter
then goes and paints the room.
One could say that the Painter
has no state and is an instance of a Helper
class.
Option 2: you have been provided a PainterFactory
instance which you tell $painter_factory->createPainter()->paintRoom()
. The Painter
then goes and paints the room.
In this situation the PainterFactory
is the one without state and is in instance of a Helper
class. We'll see why Painter
will have state in the next situation.
In the second situation, the Painter
instantiation requires one parameter. A Painter
is instantiated by: new Painter(paint_thickness)
. The paint thickness determines how thick the paint layers on the walls should be.
Option1: you have been provided a Painter
instance whom you tell $painter->paintRoom(paint_thickness)
. The Painter
then goes and paints the room. Imagine that the Painter
is only certified/capable to apply paint of a certain thickness.
It is more obvious now that the Painter
is a Helper
class.
Option 2: you have been provided a PainterFactory
instance which you tell $painter_factory->createPainter(paint_depth)->paintRoom()
. The Painter
then goes and paints the room.
In this situation the PainterFactory
remains to be the Helper
class. The Painter
will have proved to have state: it contains a paint_thickness
property. The Painter
is not a Helper
class for this option.
The reason I think Painter
is a Helper
class in the first situation, and PainterFactory
is a Helper
class in the second situation is that it will never be necessary to have more than one instance of a Painter
class.
To summarize: it is often possible to either use one instance of a class for certain functionality or to rewrite the class to actually contain state. The first option represents the use of Helper
classes. Helper
classes are often criticized as being procedural and static in nature and thus bad in OOP and a potential technical debt.
However, instantiation of objects which do have state must either happen through a Helper
factory (as we saw in the example) or by direct instantiation. Since the Helper
factory is still a Helper
, it seems that the use of Helper
classes is still not completely avoided - and direct instantiation is essentially static in nature as well.
So my question is, are Helper
classes really bad? Should one favor an object with state over an object without state but with behavior? But then again, what about small objects that don't really have behavior other than the behavior that exists in their one method (e.g. imagine rewriting a large Helper
class called Math
into separate classes such as Multiplication
, Addition
, Division
etc.).
What are some good general rules to choose an option in a Painter
situation?