The encapsulation principle says : "You should hide the attributes of your class and only make them accessible via methods. This guarantees the validity of class invariants."
I'm totally agree with this principle, but i've been wondering : "does sharing the same object between several classes break encapsulation ?"
Example : we have an object of a class A.
It seems ridiculous because the point of OOP is let objects communicate between them, but the thing is a class that has a reference to this shared object can modify without passing through the methods of other classes so logically it breaks the encapsulation ?
For example : B modify the state of the object A without the agreement of C because it doesn't pass with its methods ? Same for C that doesn't pass via B.
I'm so confused with all these principles, I feel like they're all contradicting each other. Do I perhaps misunderstand the principle of encapsulation ?
public class A {
private int value = 0;
public void increment(){
value += 1;
}
public void decrement() {
value -= 1;
}
}
public class B {
private final A a;
public B(A a){
this.a = a;
}
public void doSomething(){
// Do some stuff
a.increment();
}
}
public class C {
private final A a;
public C(A a){
this.a = a;
}
public void doSomethingElse(){
// Do some stuff
a.decrement();
}
}
Yes, sharing mutable state like this is indeed a light break of encapsulation. A few important things to realize:
Lots of classes are immutable. You can't change anything about them. Trivially, strings, for example. If 2 instances both 'share' a string, and one them calls x.toLowerCase()
, nothing happens. Because toLowerCase()
changes nothing. It makes an entirely new string and returns it. If one class writes x = x.toLowerCase()
, they are updating their reference - it does not modify the reference (nor the object) that the other class's field is referring to.
Sharing immutables therefore does not run into this issue.
Here's one rule that has no exceptions: "Every rule has exceptions".
The principle of encapsulation is a guide. And, like all guidelines, true expertise involves knowing when to ignore them.
As a general principle, encapsulation has advantages.
But then, so does sharing state.
The advantages and disadvantages are completely different. So, it's a tradeoff: Code that you write that breaks encapsulation in certain ways has certain disadvantages. For example, it's hard to update one class without at least also taking into account you need to consider the impact it has on other classes involved in the 'break'. But, often, 2 classes are fundamentally related anyway - changing one without keeping in mind the other exists is a problem anyway. At which point, that particular disadvantage is irrelevant.
There are no simple guides. Even if you feel you are really good at reading architectural documentation like this, there's no real way to fully understand how to apply it without being an experienced programmer.
It's good news, and bad news: To be a good programmer you need to do it a lot and gain 10 years experience. The downside is: Shortcuts don't exist. The upside is: Shortcuts don't exist - so you can just ignore such concerns.
If some architectural design you come up with ends up being a disaster, and if you had put more effort in avoiding encapsulation would have avoided it, [A] that doesn't mean the alternative would have been any better, and [B] well, you know how beginner programmers turn into expert programmers? Via the tool known as 'the mistake'. Cherish them. And make sure you keep in mind that post mortems on your coding decisions is crucial. You should spend more time considering the full impact of the choices you have made in the past on how maintainable your software is today, than attempting to read well-intended philosophical gobbledygook about abstract concepts like 'encapsulation'. I'm not saying things like encapsulation are meaningless; not at all. I'm saying: Without having personal experience, it's incredibly difficult to meaningfully apply the advice.
Note that my earlier advice about immutables has to adhere to all this just the same. Do not go on a spree of 'I have seen the light and ALL SHALL BE IMMUTABLE! Time to toss out all our software and start over!' - that's making the same mistake. It's a tool in the toolbox, if you keep running into issues that feel like 'encapsulation' is the issue, you might want to try a few features with more immutable architecture. Trust me, you'll run into a whole new set of disadvantages and maintenance headaches. But, now you learned, and now you can weigh the downsides of foregoing proper encapsulation against the downsides of overindexing on it. Next project you might finally be capable of making a good judgement on which tradeoff is the best for the project you're working on.