web-dev-qa-db-ja.com

同じロジックで異なるオブジェクトを処理するメソッドを作成する

同じロジックを実行するが、異なるオブジェクトを処理する次のメソッドがあると仮定します(これは疑似コードです)。


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を関数型言語として扱っています。

6
hb0009

CapitalExploreupdateOrInsertメソッドがExploreCapitalオブジェクトを識別するための統一された方法を提供していないという事実が問題の根本であるかのように私には見えます。

私はこれを一般化し、それらのメソッドを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.setIdIEntity.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を実装するすべてのクラスで機能します。getNumbergetCodeを取り巻く詳細について心配する必要はなく、一般化されたIEntity.getIdentifierメソッドで機能します(具象クラス自体がこの問題を処理します)。

updateOrInsertのロジックはすでに各リポジトリに継承されているため、ロジックを複製/コピーして貼り付ける必要はありません。

6
Ben Cottrell

「Java Genericsを間違った方法で使用しているというコメントを受け取った」

これも私の印象です。ジェネリックリストに要素を配置し、そのリストを操作する場合、通常、要素のタイプを処理したり、要素のタイプを気にする必要はありません。要素を調べて、そのタイプに基づいて行動します。これは、そもそもジェネリックを使用する目的に反します。

物事を分割し、ジェネリックではなく明示的なアイテムタイプを使用する必要があると思います。次に、ExploreとCapitalに実際に共通しているものがあるとすれば、それを考えてみてください。探索と資本の共通の基本型を使用し、ポリモーフィズムを使用することをお勧めします(基本型要素のリストを使用し、それを操作します)。ただし、共通のベースがない場合は、実際に1つのことを処理していないときに強制的に1つのことを実行するよりも、似たようなコードを使用することをお勧めします。

2
Martin Maat

そのようにしてはいけないには、多くの理由があります。私はそれが何が間違っているのか、そしてあなたがあなたのやり方をしている場合にも何をする必要があるのか​​を説明しようとします:

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ブロックが機能します。

0
Engineert