Lesson 1 - Servant
We use the Servant design pattern if we want to add additional functionality to several classes without interfering with their interfaces. The servant thus adds new functionality to us without having to be in the class. It's an idiom. So it is not included among the "right" design patterns from <acronym title="Gang of Four">GoF</acronym>. However, its use is sometimes very useful.
Motivation
Sometimes we can get into a situation where we have a certain group of classes to which we want to add additional functionality. By direct implementation, however, we would have to copy this functionality to each of them, which would be a problem when programming or expanding the whole application (not to mention the fact that this violates the <acronym title="Don't Repeat Yourself - don't repeat yourself!">DRY</acronym> principle). Another option, of course, is to define a common ancestor, but this is not always possible. The functionality that we want to extend the classes to may not be related to them at all. Classes would then have to take care of more things at once (the <acronym title="Single Responsibility Principle - one entity is responsible for just one task">CFP</acronym> principle, which is also very important in designing). In this case, we can separate the functionality into a separate class (we call it Servant).
Pattern
It is actually a box for methods that serve instances of a particular group
of classes. A servant is a separate class that contains methods that
manipulate instances of the original classes (hereinafter only
serviced). If we want to fulfil a certain task after a serviced class
(for example, smooth scrolling - animation), we ask the servant for this task,
to whom we pass an instance of the serviced class. The servant will then
communicate with the served classes via some interface (e.g.
IMovable
- in this case it will provide methods like
setPoint()
and getPoint()
so that we can move the
object gradually). Of course, we must also implement this interface for all
classes that the servant will work with.
Basically, we can implement the Servant pattern in two ways: the serviced class does not need to know anything about the servant (example - UML diagram 1), or, conversely, the user does not need to know anything about the servant (example - UML diagram 2). In the first case, we query for a specific task of a directly served class via a servant, to whom we must pass an instance of the served class. In the latter, we don't need to know anything about a servant at all. After asking for a specific task of the serviced class, the serviced method itself calls the servant method. Both of these implementations are correct. It's up to you how you want to implement the servant.
The operation is requested by the user from the servant itself
The operation is requested by the user from the serviced class
As we can read from the diagram, each class that wants to be served
implements the IServiceable
interface. The servant then only works
with this interface. If we wanted an animation servant, we would probably need
the already mentioned getPoint()
and setPoint(Point)
methods, thanks to which we could move the serviced instance (or some
graphically the object it represents). The examples differ in the user's
connection to the servant. While in the first case the user has a direct link to
the servant, in the second it is called by the served classes and the user
therefore has no direct link to it, which can be used for encapsulation, for
example.
Conclusion
We will use the Servant pattern if we want to add an ability to a group of classes, and we do not want to implement it in the original class. It is implemented by a separate class that manipulates the serviced instance (or its interface). This is an idiom and is therefore not in GoF.