3

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:

  1. I'm changing the contract of Order class without adding business value, ubt for "technical" reasons (an UPDATE in a database)
  2. 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.

Héctor
  • 24,444
  • 35
  • 132
  • 243

1 Answers1

2

This is an excellent observation in my opinion. No, I don't consider adding any methods just for technical reasons acceptable, especially getters. I must admit however, that the majority of people I've worked with would just add the getters and would not think about it in detail as you do.

Ok, so how do we solve the problem of persisting something we can't get access to? Well, just ask the object to persist itself.

You can have a persist() (or whatever) method on the object itself. This is ok, since it is part of the business. If it is not, think about what is. Is it sendToBackend() maybe? This does not mean you have to put the details of persistence into the object!

The method itself can be as removed from actual persistence as you like. You can give it interfaces as parameters, or it can return some other object that can be used further down the line.

See these other answers about the same problems for presentation:

Returning a Data Structure to Display information

Encapsulation and Getters

Robert Bräutigam
  • 7,514
  • 1
  • 20
  • 38
  • Thank you for answering Robert. It's difficult to me to see `persist()` method being part of the business, actually. What do you think about the approach I have just posted? Check the edited question. Thank you again – Héctor Oct 18 '17 at 10:43
  • 1
    I like that idea too, although it can only be used if all logic is delegated to the DB, which is not always the case. But sure, you don't need to have a `persist()` method if the object knows when to do it anyway. For example the `finish()` logic could include persisting the current state into database automatically. It all depends on the exact requirements and semantics. – Robert Bräutigam Oct 18 '17 at 11:58