简体   繁体   中英

How to conciliate the best Object Oriented Programming practices in getters and setters with the needs of ORMs frameworks?

In many of systems I have worked in, the class that represents a model is a POJO, and we map their fields to columns (in relational databases) or attributes (in some NoSQL databases). So, em many ORMs, it is mandatory to have accessors methods to take and bring data from/to database. However, the best practices in Object Oriented Programming say that we should not expose the intern structure of our objects. Instead, we must expose operations that change the internal state of objects e maintain the state consistency of this object

Let's give an example. Let's say we have a class Client. This class has an ID, the customer's name e its date of last change. We cannot change this data directly, but we want to persist them. If we want to modify the client's name, we must also change the ID and the last change date.

The ORM needs of following getters and setters methods, so that we have:

@Entity
public class Client {

  @Id
  private Long id;

  @Index
  private String name;

  private Date lastChange;

  public Long getId() {
    return this.id;
  }

  public String getName() {
    return this.name;
  }

  public Date lastChange() {
    return this.lastChange;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public void setString(String name) {
    this.name = name;
  }

  public void setLastChange() {
    this.lastChange = latChange;
  }

}

The way it is, anyone, besides the ORM, could change the ID and last change date, causing unwanted effects on the rest of the system.

Instead, if we wanted to respect the Oriented Object rules, we have something like this:

@Entity
public class Client {

  @Id
  private Long id;

  @Index
  private String name;

  private Date lastChange;

  public Client(Long id, String name) {
    this.id = id;
    this.name = name;
    this.lastChange = new Date();
  }

  public Long getId() {
    return this.id;
  }

  public String getName() {
    return this.name;
  }

  public void changeName(String name) {
    this.name = name;
    this.id = newIdFromClient();
    this.lastChange = new Date();
  }

  private Long getNewIdFromClient() {
    return (new Random()).nextLong();
  }

}

So, my question is: how we conciliate the best practices of Object Oriented Programming with the needs of getters and setters in ORMs frameworks?

Off Topic:

In many of systems I have worked in, the class that represents a model is a POJO,

A POJO is a Java class that does not implement interfaces or extend other (abstract) classes in order to fulfill the needs of a framework. What you most likely refer to is a data transfer object (DTO) or bean (which usually are POJOs).

On topic:

However, the best practices in Object Oriented Programming say that we should not expose the intern structure of our objects. Instead, we must expose operations that change the internal state of objects e maintain the state consistency of this object

DTOs/beans are pure data structure and not objects by means of OOP. Unfortunately in Java we have to use the same class-concept to create them (unlike eg Kotlin, where we have a special keyword for data classes).

Therefore it is OK for datastructure to expose their inner structure because that's what they are all about.

However, in classes that build your business logic working on DTOs/beans you should strictly follow the information hiding principle tough and avoid getters/setters.

Some would argue you shouldn't use ORM . Give that article a read. It will challenge your preconceptions even if you don't necessarily agree with it.

You're very much right that ORM relies on the java "bean" pattern of getters and setters which breaks encapsulation and forces objects to be mutable unnecessarily.

In my mind, the best way to give yourself the benefits of both worlds is to use two different classes. The following is just pseudo-code to illustrate the general idea:

@Entity
class ORMFoo
{
   private int bar;

   ORMFoo(int bar){...}

   void setBar() {...}
   int  getBar() {...}
   Foo  getImmutable() { return new Foo(bar); }
}

class Foo
{
   private final int bar;
   Foo(final int bar) {...}
   save(ORMManager manager) { manager.update(new ORMFoo(bar)); }
}

Use the latter throughout most of your application. Use the former only as a transfer object.

An ORM like JDXA (Disclaimer: I am the architect of JDXA ORM for Android) can solve the problem by using a non-standard alternate setter method (for example, with the following signature) to set an attribute value:

void setAttribValue(String attribName, Object Value)

So, rest of the developers would not see and (ab)use a 'standard' bean setter method but the ORM can employ this alternate method for its own use. For example, in your case, JDXA can work with the following class definition:

public class Client {

  private Long id;
  private String name;
  private Date lastChange;
  private int something;

  public Client() {
  }

  public Client(Long id, String name) {
    this.id = id;
    this.name = name;
    this.lastChange = new Date();
  }

  public Long getId() {
    return this.id;
  }

  public String getName() {
    return this.name;
  }

  public Date getLastChange() {
    return this.lastChange;
  }

  public int getSomething() {
     return something;
  }

  // May be OK to expose this bean style setter method
  public void setSomething(int something) {
    this.something = something;
  }

  // JDXA ORM will automatically use this method to set values of those 
  // attributes for which it cannot find a 'regular' setter method. 
  public void setAttribValue(String attribName, Object value) {
    switch (attribName) {
        case "id":
           this.id = (Long) value;
           break;
        case "name":
           this.name = (String) value;
           break;
        case "lastChange":
           this.lastChange = (Date) value;
           break;
        default:
           throw new IllegalArgumentException("Invalid attribute name: " + attribName);
      }
      return;
  }

  public void changeName(String name) {
    this.name = name;
    this.id = getNewIdFromClient();
    this.lastChange = new Date();
  }

  private Long getNewIdFromClient() {
    return (new Random()).nextLong();
  }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM