I am having trouble reconciling the Single Responsibility Principle with encapsulation. It seems like splitting responsibilities amongst classes requires exposing a lot of data. As an example, consider some object called DataPoints
. DataPoints
is filled with x and y coordinates, among other things. I can create a Generator class that fills DataPoints
. Now, let's say I want to plot those data points. Clearly, that's a separate responsibility that might come from a class called DataPointsPlotter
. But to plot the data, I need to know what the internal x and y coordinates are. With a single class handling both, that's no problem. x and y are internal variables, but both a create() and a print() method have access to those. I can expose x and y (perhaps through getters/setters--ugh) or I can pass the DataPoints
structure to the Plotter class, but it still needs to get inside to get x and y. I can have an instance of Plotter declared in the DataPoints
class to which I send x and y. But that's still an exposure.
How can I in this example plot x and y using the plotter without violating encapsulation?
class DataPoint {
protected x, y
public function construct(theX, theY) {
x = theX
y = theY
}
public function getX {
return x
}
public function getY {
return y
}
}
This encapsulates the responsibility of representing a datapoint in a known format. Assuming there's also some sanity checking going on to confirm that x
and y
have good values, this is one responsibility which is being encapsulated here.
class Plotter {
public function plot(DataPoint dp) {
x = dp.getX
y = dp.getY
... plot ...
}
}
The plotter accepts an instance of DataPoint
and plots it. That's also one responsibility: plot an instance of DataPoint
. It does not require Plotter
to have any knowledge of the internal structure of DataPoint
. It just requires a defined and stable interface how Plotter
can get the necessary data from DataPoint
. Which are the getters getX
and getY
. You can change the internals of DataPoint
around however you want as long as its interface stays the same. And the responsibility of DataPoint
is to hold a datapoint and give other code access to its data in a defined way. If there was no way to get data out of DataPoint
, its existence would be quite useless.
So no, SRP and encapsulation are not at odds with each other. You just need to be clear about what exactly the responsibility is.