同じロジックを実行するが、異なるオブジェクトを処理する次のメソッドがあると仮定します(これは疑似コードです)。
private <E> List<E> updateOrInsert(List<E> list) {
if (list == null && list.size() == 0) {
return list;
}
if (list.get(0) instanceof Explore) {
List<String> numbers = new ArrayList<>();
List<Explore> explorisList = (List<Explore>) list;
explorisList.forEach(item -> {numbers.add(item.getNumber());});
List<Explore> exploreDB = explorisRepository.findAllByNumberIn(numbers);
Map<String, Explore> map = new HashMap<>();
exploreDB.forEach(item -> {
map.put(item.getNumber(), item);
});
explorisList.forEach(item -> {
Explore itemMap = map.get(item.getNumber());
if (itemMap != null) {
item.setId(itemMap.getId());
}
});
return (List<E>) explorisRepository.saveAll(explorisList);
} else if (list.get(0) instanceof Capital) {
List<String> codes = new ArrayList<>();
List<Capital> listOfCompanies = (List<Capital>) list;
listOfCompanies.forEach(item -> codes.add(item.getCode()));
List<Capital> capitalDB = companyRepository.findAllByCodeIn(codes);
Map<String, Capital> map = new HashMap<>();
capitalDB.forEach(item -> {
map.put(item.getCode(), item);
});
((List<Capital>) list).forEach(item -> {
Capital itemMap = map.get(item.getCode());
if (itemMap != null) {
item.setId(itemMap.getId());
}
});
return (List<E>) companyRepository.saveAll(capitalSet);
//if statement continues but you got the picture
} else {
throw new UnsupportedOperationException("Unsupported operation for " + list.getClass().getName());
}
// ... etc.
ご覧のとおり、各リストは同じビジネスロジックを持っていますが、異なるオブジェクトを扱います。
それらを異なる方法に分離する方が良いですか?なぜ?
そのようなアプローチについての私の議論は、関数/メソッドは1つのロジックのみを表すべきであると私は想定しているということです。したがって、同じロジックを実行する別の関数を作成することは悪い設計です。
保守性はどうですか?
また、Java Genericsを間違った方法で使用しており、Javaを関数型言語として扱っています。
Capital
とExplore
がupdateOrInsert
メソッドがExplore
とCapital
オブジェクトを識別するための統一された方法を提供していないという事実が問題の根本であるかのように私には見えます。
私はこれを一般化し、それらのメソッドをinterface
およびExplore
が実装できるCapital
に入れることを検討します。たとえば、getCode
およびgetNumber
の代わりに、getIdentifier
として一般化できます。
interface IEntity {
String getIdentifier();
void setId(int id);
int getId();
}
このパターン内でサポートする必要がある各クラスは、汎用インターフェースを使用して確実に操作できるように、次のようになります。
class Capital implements IEntity {
@Override public void setId(int id) { _id = id; }
@Override public int getId() { return _id; }
@Override public String getIdentifier() { return getCode(); }
public string getCode() { return _code; }
}
class Explore implements IEntity {
@Override public void setId(int id) { _id = id; }
@Override public int getId() { return _id; }
@Override public String getIdentifier() { return getNumber(); }
public string getNumber() { return _number; }
}
これにより、updateOrInsert
メソッドが個々のオブジェクトを操作するための統一された方法が提供されます。
次の問題はリポジトリに関するものです。
updateOrInsert
メソッドがリポジトリとどの程度密接に関連しているかを考えると、このメソッドをabstract
クラスに入れることをお勧めします。これには、各リポジトリが拡張でき、find
およびsave
メソッドのシグネチャも含まれます。
例えば:
abstract class RepositoryBase<T extends IEntity> {
public abstract List<T> findAllByIdentifierIn(List<String> list);
public abstract List<T> saveAll(List<T> list);
public List<T> updateOrInsert(List<T> list) {
/* TODO: Implementation here */
}
}
<T extends IEntity>
を使用して制約を置くことにより、updateOrInsert
メソッドはIEntity.setId
、IEntity.getId
およびIEntity.getIdentifier
を使用できるようになります
この方法では、Explore
およびCapital
のリポジトリでBaseRepository
を拡張して、updateOrInsert
の共通基本クラスロジックを利用できます。
class ExplorisRepository extends RepositoryBase<Explore> {
@Override public List<Explore> findAllByIdentifierIn(List<String> list) {}
@Override public void saveAll(List<Explore> list) {}
}
class CapitalRepository extends RepositoryBase<Capital> {
@Override public List<Capital> findAllByIdentifierIn(List<String> list) {}
@Override public void saveAll(List<Capital> list) {}
}
これらのリポジトリクラスは、RepositoryBase
またはCapital
の実際の型を提供することにより、Explore
を拡張します。つまり、Repositoryクラスはその情報をベースに渡すので、updateOrInsert
はさまざまなタイプを考慮する必要がなくなります。 <T>
はExplore
またはCapital
または他のエンティティのいずれかとして強く型付けされるため、使用する正確な型を常に認識しており、キャストや型チェックを実行する必要はありません。
// implementation of RepositoryBase<T>.updateOrInsert()
public List<T> updateOrInsert(List<T> list){
if (list == null || list.size() == 0) return list;
List<String> identifiers = new ArrayList<>();
list.forEach(item -> {identifiers.add(item.getIdentifier());});
List<T> itemsFromDb = this.findAllByIdentifierIn(identifiers);
Map<String, T> map = new HashMap<>();
itemsFromDb.forEach(item -> {
map.put(item.getIdentifier(), item);
});
list.forEach(item -> {
T itemMap = map.get(item.getIdentifier());
if (itemMap != null) {
item.setId(itemMap.getId());
}
});
return saveAll(list);
}
これは、IEntity
を実装するすべてのクラスで機能します。getNumber
とgetCode
を取り巻く詳細について心配する必要はなく、一般化されたIEntity.getIdentifier
メソッドで機能します(具象クラス自体がこの問題を処理します)。
updateOrInsert
のロジックはすでに各リポジトリに継承されているため、ロジックを複製/コピーして貼り付ける必要はありません。
「Java Genericsを間違った方法で使用しているというコメントを受け取った」
これも私の印象です。ジェネリックリストに要素を配置し、そのリストを操作する場合、通常、要素のタイプを処理したり、要素のタイプを気にする必要はありません。要素を調べて、そのタイプに基づいて行動します。これは、そもそもジェネリックを使用する目的に反します。
物事を分割し、ジェネリックではなく明示的なアイテムタイプを使用する必要があると思います。次に、ExploreとCapitalに実際に共通しているものがあるとすれば、それを考えてみてください。探索と資本の共通の基本型を使用し、ポリモーフィズムを使用することをお勧めします(基本型要素のリストを使用し、それを操作します)。ただし、共通のベースがない場合は、実際に1つのことを処理していないときに強制的に1つのことを実行するよりも、似たようなコードを使用することをお勧めします。
そのようにしてはいけないには、多くの理由があります。私はそれが何が間違っているのか、そしてあなたがあなたのやり方をしている場合にも何をする必要があるのかを説明しようとします:
1。DRY
そのようにすると、Do n't Repeat Yourself(DRY)に反対します。タイプごとに同じコードをコピーして貼り付けます。優れた設計により、データのタイプを知る必要のない1つの方法で実行でき、必要に応じて変更できる場所は1つだけです。
2。タイプごとの制限
このようにしてはいけないもう1つの理由は、型ごとに1つのロジックでコードを制限することです。すべてのCapital
を取得する必要があると仮定します。コードでオブジェクトタイプをチェックしてロジックを実行するとき、別のサービスを呼び出す柔軟性を与えることはできません。
_public interface ICapitalService
{
List<Capital> GetAll();
}
public class LocalCapitalService : ICapitalService
{
public List<Capital> GetAll()
{
// get from db.
}
}
public class RemoteCapitalService : ICapitalService
{
public List<Capital> GetAll()
{
// get from web service
}
}
_
誰かがリモートサービスから資金を得る人もいれば、dbから資金を得たい人もいます。ただし、Capital
のタイプをチェックし、コードで特定のロジックを実行することにより、制限します。 Capital
に別のオプションを追加することはできません。
。子クラスタイプのみがチェックされていることを確認する必要があります。
instanceof
でタイプを確認すると、親クラスの場合もtrueが返されます。したがって、必要なコードブロックに到達できない可能性があります。
_if (list.get(0) instanceof Car) {
//...
}
else if (list.get(0) instanceof Volvo)
{
//..
}
_
ボルボは自動車であるため、ボルボブロックに到達できず、手動で管理する必要があります。
4。ソフトウェア言語の制限
リストの最初のアイテムを取得して、アイテムタイプのリストをチェックしています(list.get(0)
)。リストの最初の項目がnullの場合、instanceof
はfalseを返し、ロジックは期待どおりに機能せず、最後のelse
ブロックが機能します。