Having getters and setters does not in itself break encapsulation. What does break encapsulation is automatically adding a getter and a setter for every data member (every field, in java lingo), without giving it any thought. While this is better than making all data members public, it is only a small step away.
The point of encapsulation is not that you should not be able to know
or to change the object's state from outside the object, but that you
should have a reasonable policy for doing it.
Some data members may be entirely internal to the object, and should
have neither getters nor setters.
Some data members should be read-only, so they may need getters but
not setters.
Some data members may need to be kept consistent with each other. In
such a case you would not provide a setter for each one, but a single
method for setting them at the same time, so that you can check the
values for consistency.
Some data members may only need to be changed in a certain way, such
as incremented or decremented by a fixed amount. In this case, you
would provide an increment()
and/or decrement()
method, rather
than a setter.
Yet others may actually need to be read-write, and would have both a getter
and a setter.
Consider an example of a class Person
. Let's say a person has a name, a social security number, and an age. Let's say that we do not allow people to ever change their names or social security numbers. However, the person's age should be incremented by 1 every year. In this case, you would provide a constructor that would initialize the name and the SSN to the given values, and which would initialize the age to 0. You would also provide a method incrementAge()
, which would increase the age by 1. You would also provide getters for all three. No setters are required in this case.
In this design you allow the state of the object to be inspected from outside the class, and you allow it to be changed from outside the class. However, you do not allow the state to be changed arbitrarily. There is a policy, which effectively states that the name and the SSN cannot be changed at all, and that the age can be incremented by 1 year at a time.
Now let's say a person also has a salary. And people can change jobs at will, which means their salary will also change. To model this situation we have no other way but to provide a setSalary()
method! Allowing the salary to be changed at will is a perfectly reasonable policy in this case.
By the way, in your example, I would give the class Fridge
the putCheese()
and takeCheese()
methods, instead of get_cheese()
and set_cheese()
. Then you would still have encapsulation.
public class Fridge {
private List objects;
private Date warranty;
/** How the warranty is stored internally is a detail. */
public Fridge( Date warranty ) {
// The Fridge can set its internal warranty, but it is not re-exposed.
setWarranty( warranty );
}
/** Doesn't expose how the fridge knows it is empty. */
public boolean isEmpty() {
return getObjects().isEmpty();
}
/** When the fridge has no more room... */
public boolean isFull() {
}
/** Answers whether the given object will fit. */
public boolean canStore( Object o ) {
boolean result = false;
// Clients may not ask how much room remains in the fridge.
if( o instanceof PhysicalObject ) {
PhysicalObject po = (PhysicalObject)o;
// How the fridge determines its remaining usable volume is a detail.
// How a physical object determines whether it fits within a specified
// volume is also a detail.
result = po.isEnclosedBy( getUsableVolume() );
}
return result;
}
/** Doesn't expose how the fridge knows its warranty has expired. */
public boolean isPastWarranty() {
return getWarranty().before( new Date() );
}
/** Doesn't expose how objects are stored in the fridge. */
public synchronized void store( Object o ) {
validateExpiration( o );
// Can the object fit?
if( canStore( o ) ) {
getObjects().add( o );
}
else {
throw FridgeFullException( o );
}
}
/** Doesn't expose how objects are removed from the fridge. */
public synchronized void remove( Object o ) {
if( !getObjects().contains( o ) ) {
throw new ObjectNotFoundException( o );
}
getObjects().remove( o );
validateExpiration( o );
}
/** Lazily initialized list, an implementation detail. */
private synchronized List getObjects() {
if( this.list == null ) { this.list = new List(); }
return this.list;
}
/** How object expiration is determined is also a detail. */
private void validateExpiration( Object o ) {
// Objects can answer whether they have gone past a given
// expiration date. How each object "knows" it has expired
// is a detail. The Fridge might use a scanner and
// items might have embedded RFID chips. It's a detail hidden
// by proper encapsulation.
if( o implements Expires && ((Expires)o).expiresBefore( today ) ) {
throw new ExpiredObjectException( o );
}
}
/** This creates a copy of the warranty for immutability purposes. */
private void setWarranty( Date warranty ) {
assert warranty != null;
this.warranty = new Date( warranty.getTime() )
}
}
As stated in quite a few answers and comments, DTOs are appropriate and useful in some situations, especially in transferring data across boundaries (e.g. serializing to JSON to send through a web service). For the rest of this answer, I'll more or less ignore that and talk about domain classes, and how they can be designed to minimize (if not eliminate) getters and setters, and still be useful in a large project. I also won't talk about why remove getters or setters, or when to do so, because those are questions of their own.
As an example, imagine that your project is a board game like Chess or Battleship. You might have various ways of representing this in a presentation layer (console app, web service, GUI,etc.), but you also have a core domain. One class you might have is Coordinate
, representing a position on the board. The "evil" way to write it would be:
public class Coordinate
{
public int X {get; set;}
public int Y {get; set;}
}
(I'm going to be writing code examples in C# rather than Java, for brevity and because I'm more familiar with it. Hopefully that's not a problem. The concepts are the same and the translation should be simple.)
Removing Setters: Immutability
While public getters and setters are both potentially problematic, setters are the far more "evil" of the two. They're also usually the easier to eliminate. The process is a simple one- set the value from within the constructor. Any methods which previously mutated the object should instead return a new result. So:
public class Coordinate
{
public int X {get; private set;}
public int Y {get; private set;}
public Coordinate(int x, int y)
{
X = x;
Y = y;
}
}
Note that this doesn't protect against other methods in the class mutating X and Y. To be more strictly immutable, you could use readonly
(final
in Java). But either way- whether you make your properties truly immutable or just prevent direct public mutation through setters- it does the trick of removing your public setters. In the vast majority of situations, this works just fine.
Removing Getters, Part 1: Designing for Behavior
The above is all well and good for setters, but in terms of getters, we actually shot ourselves in the foot before even starting. Our process was to think of what a coordinate is- the data it represents- and create a class around that. Instead, we should have started with what behavior we need from a coordinate. This process, by the way, is aided by TDD, where we only extract classes like this once we have a need for them, so we start with the desired behavior and work from there.
So let's say that the first place you found yourself needing a Coordinate
was for collision detection: you wanted to check if two pieces occupy the same space on the board. Here's the "evil" way (constructors omitted for brevity):
public class Piece
{
public Coordinate Position {get; private set;}
}
public class Coordinate
{
public int X {get; private set;}
public int Y {get; private set;}
}
//...And then, inside some class
public bool DoPiecesCollide(Piece one, Piece two)
{
return one.X == two.X && one.Y == two.Y;
}
And here's the good way:
public class Piece
{
private Coordinate _position;
public bool CollidesWith(Piece other)
{
return _position.Equals(other._position);
}
}
public class Coordinate
{
private readonly int _x;
private readonly int _y;
public bool Equals(Coordinate other)
{
return _x == other._x && _y == other._y;
}
}
(IEquatable
implementation abbreviated for simplicity). By designing for behavior rather than modelling data, we've managed to remove our getters.
Note this is also relevant to your example. You may be using an ORM, or display customer information on a website or something, in which case some kind of Customer
DTO would probably make sense. But just because your system includes customers and they are represented in the data model does not automatically mean you should have a Customer
class in your domain. Maybe as you design for behavior, one will emerge, but if you want to avoid getters, don't create one pre-emptively.
Removing Getters, Part 2: External Behaviour
So the above is a good start, but sooner or later you will probably run into a situation where you have behavior which is associated with a class, which in some way depends on the class's state, but which doesn't belong on the class. This sort of behavior is what typically lives in the service layer of your application.
Taking our Coordinate
example, eventually you'll want to represent your game to the user, and that might mean drawing to the screen. You might, for example, have a UI project which uses Vector2
to represent a point on the screen. But it would be inappropriate for the Coordinate
class to take charge of converting from a coordinate to a point on the screen- that would be bringing all sorts of presentation concerns into your core domain. Unfortunately this type of situation is inherent in OO design.
The first option, which is very commonly chosen, is just expose the damn getters and say to hell with it. This has the advantage of simplicity. But since we're talking about avoiding getters, let's say for argument's sake we reject this one and see what other options there are.
A second option is to add some kind of .ToDTO()
method on your class. This- or similar- may well be needed anyway, for example when you want to save the game you need to capture pretty much all of your state. But the difference between doing this for your services and just accessing the getter directly is more or less aesthetic. It still has just as much "evil" to it.
A third option- which I've seen advocated by Zoran Horvat in a couple of Pluralsight videos- is to use a modified version of the visitor pattern. This is a pretty unusual use and variation of the pattern and I think people's mileage will vary massively on whether it's adding complexity for no real gain or whether it's a nice compromise for the situation. The idea is essentially to use the standard visitor pattern, but have the Visit
methods take the state they need as parameters, instead of the class they're visiting. Examples can be found here.
For our problem, a solution using this pattern would be:
public class Coordinate
{
private readonly int _x;
private readonly int _y;
public T Transform<T>(IPositionTransformer<T> transformer)
{
return transformer.Transform(_x,_y);
}
}
public interface IPositionTransformer<T>
{
T Transform(int x, int y);
}
//This one lives in the presentation layer
public class CoordinateToVectorTransformer : IPositionTransformer<Vector2>
{
private readonly float _tileWidth;
private readonly float _tileHeight;
private readonly Vector2 _topLeft;
Vector2 Transform(int x, int y)
{
return _topLeft + new Vector2(_tileWidth*x + _tileHeight*y);
}
}
As you can probably tell, _x
and _y
aren't really encapsulated any more. We could extract them by creating an IPositionTransformer<Tuple<int,int>>
which just returns them directly. Depending on taste, you may feel this makes the entire exercise pointless.
However, with public getters, it's very easy to do things the wrong way, just pulling data out directly and using it in violation of Tell, Don't Ask. Whereas using this pattern it's actually simpler to do it the right way: when you want to create behaviour, you'll automatically start by creating a type associated with it. Violations of TDA will be very obviously smelly and probably require working around a simpler, better solution. In practice, these points make it much easier to do it the right, OO, way than the "evil" way that getters encourage.
Finally, even if it isn't initially obvious, there may in fact be ways to expose enough of what you need as behavior to avoid needing to expose state. For example, using our previous version of Coordinate
whose only public member is Equals()
(in practice it would need a full IEquatable
implementation), you could write the following class in your presentation layer:
public class CoordinateToVectorTransformer
{
private Dictionary<Coordinate,Vector2> _coordinatePositions;
public CoordinateToVectorTransformer(int boardWidth, int boardHeight)
{
for(int x=0; x<boardWidth; x++)
{
for(int y=0; y<boardWidth; y++)
{
_coordinatePositions[new Coordinate(x,y)] = GetPosition(x,y);
}
}
}
private static Vector2 GetPosition(int x, int y)
{
//Some implementation goes here...
}
public Vector2 Transform(Coordinate coordinate)
{
return _coordinatePositions[coordinate];
}
}
It turns out, perhaps surprisingly, that all the behavior we really needed from a coordinate to achieve our goal was equality checking! Of course, this solution is tailored to this problem, and makes assumptions about acceptable memory usage/performance. It's just an example that fits this particular problem domain, rather than a blueprint for a general solution.
And again, opinions will vary on whether in practice this is needless complexity. In some cases, no such solution like this might exist, or it might be prohibitively weird or complex, in which case you can revert to the above three.
Best Answer
I remember having a similar argument with my lecturer when learning C++ at university. I just couldn't see the point of using getters and setters when I could make a variable public. I understand better now with years of experience and I've learned a better reason than simply saying "to maintain encapsulation".
By defining the getters and setters, you will provide a consistent interface so that if you should wish to change your implementation, you're less likely to break dependent code. This is especially important when you classes are exposed via an API and used in other apps or by 3rd parties. So what about the stuff that goes into the getter or setter?
Getters are generally better off implemented as a simple dumbed-down passthrough for access to a value because this makes their behaviour predictable. I say generally, because I've seen cases where getters have been used to access values manipulated by calculation or even by conditional code. Generally not so good if you are creating visual components for use at design time, but seemingly handy at run time. There's no real difference however between this and using a simple method, except that when you use a method, you are generally more likely to name a method more appropriately so that the functionality of the "getter" is more apparent when reading the code.
Compare the following:
and
The second option makes it clear that the value is being calculated, whereas the first example tells you that you are simply returning a value without knowing anything about the value itself.
You could perhaps argue that the following would be clearer:
The problem however is that you are assuming that the value has already been manipulated elsewhere. So in the case of a getter, while you may wish to assume that something else might be going on when you are returning a value, it is difficult to make such things clear in the context of a property, and property names should never contain verbs otherwise it makes it difficult to understand at a glance whether the name used should be decorated with parentheses when accessed.
Setters are a slightly different case however. It is entirely appropriate that a setter provide some additional processing in order to validate the data being submitted to a property, throwing an exception if setting a value would violate the defined boundaries of the property. The problem that some developers have with adding processing to setters however is that there is always a temptation to have the setter do a little more, such as perform a calculation or a manipulation of the data in some manner. This is where you can get side-effects that can in some cases be either unpredictable or undesirable.
In the case of setters I'm always apply a simple rule of thumb, which is to do as little as possible to the data. For example, I will usually allow either boundary testing, and rounding so that I can both raise exceptions if appropriate, or avoid unnecessary exceptions where they can be sensibly avoided. Floating point properties are a good example where you might wish to round excessive decimal places to avoid raising an exception, while still allowing in range values to be entered with a few additional decimal places.
If you apply some sort of manipulation of the setter input, you have the same problem as with the getter, that it is difficult to allow others to know what the setter is doing by simply naming it. For example:
Does this tell you anything about what is going to happen to the value when it is given to the setter?
How about:
The second example tells you exactly what is going to happen to your data, while the first won't let you know if you value is going to be arbitrarily modified. When reading code, the second example will be much clearer in it's purpose and function.
Having getters and setters isn't about encapsulation for the sake of "purity", but about encapsulating in order to allow code to be easily refactored without risking a change to the class's interface which would otherwise break the class's compatibility with calling code. Validation is entirely appropriate in a setter, however there is a small risk is that a change to the validation could break compatibility with calling code if the calling code relies on the validation occurring in a particular way. This is a generally rare and relatively low-risk situation, but it should be noted for the sake of completeness.
Validation should happen within the context of the setter prior to actually setting the value. This ensures that if an exception is thrown, the state of your object won't change and potentially invalidate its data. I generally find it better to delegate validation to a separate method which would be the first thing called within the setter, in order to keep the setter code relatively uncluttered.
In very rare cases, maybe. In general, it is probably better not to. This is the sort of thing best left to another method.