web-dev-qa-db-ja.com

Javaで型フィールドを実装するための回避策のより良い実践

当社のメインライブラリには、次のクラスがあります。

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

UserTypeを実装するためのいくつかのオプションがあります。その1つが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));
    }
}

USERSテーブル:

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

RowMapper<User>再び、次のように私たちのライブラリで:

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"));
    }
}

ただし、メインライブラリを依存関係として使用するプロジェクトでは、UserTypeを拡張する必要がある場合があります。 UserTypeをインターフェイスに変換しても機能しません。これは、RowMapper(ライブラリ内)がUserTypeをインスタンス化する必要があるためです。

これが私が思いついたものです:

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

そしてデータベースは次のようになります:

USERS

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

USER_TYPES

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

RowMapperでは、次のようにユーザーを作成します。

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

そして実用性のために、私たちはUserTypeContainerを持つことができました:

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

それを利用できるように:

someUserDao.find(UserTypeContainer.DEFAULT);

そして、依存関係としてこのライブラリを持つプロジェクトは、

1)データベースに拡張型を持っている2)UserTypeContainerを拡張して拡張型を持たせる(上の行を利用したい場合)

これは最適なソリューションですか?これは良いプログラミング習慣ですか、それとも欠陥がありますか?他にどのようなオプションがありますか?

更新:

ライブラリに含まれる再利用可能なRowMapperを考え出す必要があります。そして、これは最適な解決策ではないと思い始めました。

2
Hasan Can Saral

オプションのUserTypeファクトリー/作成者/ビルダーをRowMapperに取得させます。 RowMapperが慣れていないUserTypeを検出した場合は、それをファクトリに渡します(指定されている場合)。

明確にするために、私はUserTypeをクラスにしますが、それでも列挙型のように見える可能性があります。

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;
  }
}

次に、FunctionInterfaceを引数として定義します...

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

次に、DAOはそのインターフェースをコンストラクターへの引数として受け取ります。

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);
      }
    }
}
0
Matthew