2

We have a class in our company's main library as follows:

public class User {
    private long id;
    private UserType type;
    // Getters, setters, constructors, etc.
}

and have several options to implement UserType. One of which is an enum:

public enum UserType {
    DEFAULT (10),
    TYPE_A  (20);

    private int type;

    UserType(int type) {
        this.type = type;
    }

    public int getType() {
        return type;
    }

    public static UserType valueOf(int value){

        for(UserType type : values()){
            if(type.getType() == value){
                return type;
            }
        }
        throw new IllegalArgumentException(String.format("Couldn't convert value : %d into a valid %s object", value, UserType.class));
    }
}

Our USERS table:

+----+------+
| id | type |
+----+------+
|  1 |   10 |
|  2 |   20 |
+----+------+

We have a RowMapper<User> again, in our library which is as follows:

public class UserRowMapper implements RowMapper<User> {
    @Override
    public User mapRow(ResultSet rs, int i) throws SQLException {
        return new User(rs.getLong("id"), UserType.valueOf(rs.getInt("type"));
    }
}

However, a project that takes the main library as a dependency might need to extend UserType. Converting UserType into an interface, again doesn't work, because of RowMapper (again in the library) will have to instantiate a UserType.

Here's what I come up with:

public class UserType {
    private int id;
    private String name;
    // Getters, setters and constructors
}

and the database becomes:

USERS:

+----+------+
| id | type |
+----+------+
|  1 |   10 |
|  2 |   20 |
+----+------+

USER_TYPES:

+----+---------+
| id |  name   |
+----+---------+
| 10 | DEFAULT |
| 20 | TYPE_A  |
+----+---------+

and in RowMapper we create a user as follows:

return new User(rs.getLong("id"), new UserType(rs.getInt("type_id"), rs.getString("type_name")));

and for utility, we could a have a UserTypeContainer:

public class UserTypeContainer {
    public static final UserType DEFAULT = new UserType(10, "DEFAULT");
    public static final UserType TYPE_A = new UserType(10, "TYPE_A");
}

so that one could make use of it:

someUserDao.find(UserTypeContainer.DEFAULT);

and the projects that have this library as dependency will have to:

1) Have the extended types in their database 2) Extend UserTypeContainer to have their extended types (if they want to make use of the line above)

Is this an optimal solution? Is this a good programming practice, or do you see any flaws? What other options do I have?

Update:

I need to come up with a reusable RowMapper contained by the library. And I started to think that this is not an optimal solution.

1
+50

Have RowMapper take an optional UserType factory/creator/builder. If RowMapper encounters a UserType it's unfamiliar with, pass it to the factory (if one was given.)

To clarify, I would make UserType a class but it could still look like an enum:

class UserType {
  public static final UserType DEFAULT = new UserType(10);
  public static final UserType TYPE_A = new UserType(20);

  private int typeId;

  protected UserType(int typeId) {
     this.typeId = typeId;
  }
}

Then, define a FunctionInterface as an argument...

@FunctionInterface
interface UserTypeCreator {
   UserType create(ResultSet rs); // I don't actually recommend using ResultSet as an argument
}

Then your DAO takes that interface as an argument to the constructor.

class SomeDao {
   private Optional<UserTypeCreator> customTypeCreator;

   public SomeDao(UserTypeCreator customTypeCreator) {
      this.customTypeCreator = Optional.ofNullable(customTypeCreator);
   }

   public UserType create(ResultSet rs) {
      int type = getType(rs);
      switch (type) {
          case 10:
             return UserType.DEFAULT;
          case 20:
             return UserType.TYPE_A;
          default:
             return customTypeCreator
                         .orElseThrow(UnknownUserTypeException::new)
                         .create(rs);
      }
    }
}
2
  • Which would be better in terms of implementing UserType, an enum or an actual class with id and name fields? Could you elaborate a bit more on if I should implement the different User classes or not, and how a utility class would look like so that the consumers of the library could use methods like someUserDao.find(UserTypeContainer.DEFAULT);? I'd greatly appreciate a bit more complete answer. – Hasan Can Saral Oct 20 '19 at 13:54
  • 1
    @HasanCanSaral added some more detail. – Matthew Oct 20 '19 at 22:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.