Let's say I have a class Order
. An Order
can be finished by calling Order.finish()
method. Internally, when an Order
is finished, a finishing date is set:
Order.java
public void finish() {
finishingDate = new Date();
}
In the application's business logic, there is no need to expose an Order
's finishingDate
, so it is a private field without a getter.
Imagine that after finishing an Order
, I want to update it in a database. For instance, I could have a DAO with an update
method:
OrderDao.java
public void update(Order order) {
//UPDATE FROM ORDERS SET ...
}
In that method, I need the internal state of the Order
, in order to update the table fields. But I said before that there is no need in my business logic to expose Order
's finishingDate
field.
If I add a Order.getFinishingDate()
method:
- I'm changing the contract of
Order
class without adding business value, ubt for "technical" reasons (anUPDATE
in a database) - I'm violating the principle of encapsulation of object oriented programming, since I'm exposing internal state.
How do you solve this? Do you consider adding getters (like "entity" classes in ORM do) is acceptable?
I have seen a different approach where class itself (implementation) knows even how to persist itself. Something like this (very naive example, it's just for the question):
public interface Order {
void finish();
boolean isFinished();
}
public class DbOrder implements Order {
private final int id;
private final Database db;
//ctor. An implementation of Database is injected
@Override
public void finish() {
db.update("ORDERS", "FINISHING_DATE", new Date(), "ID=" + id);
}
@Override
public boolean isFinished() {
Date finishingDate = db.select("ORDERS", "FINISHING_DATE", "ID=" + id);
return finishingDate != null;
}
}
public interface Database {
void update(String table, String columnName, Object newValue, String whereClause);
void select(String table, String columnName, String whereClause);
}
Apart from the performance issues (actually, it can be cached or something), I like this approach but it forces us to mock many things when testing, since all the logic is not "in-memory". I mean, the required data to "execute" the logic under test is not just a field in memory, but it's provided by an external component: in this case, the Database
.