web-dev-qa-db-ja.com

設計パターン:ENUMの既存の機能を拡張する

私が扱っている問題について説明しましょう。実稼働コードで使用するカスタム(jar)APIがあります。このAPIには、次のようなENUMが含まれています。

_public enum Cars {
   AUDI("BERLIN", "FRANKFURT", "HAMBURG", "MUNICH")
   VW("BERLIN", "FRANKFURT"),
   MERCEDES("HAMBURG", "MUNICH")

   private final String[] cityNames;

   Cars(String... cityNames) {
      this.cityNames = cityNames;
   }

   public isCityNameAvailable(String cityName) {
      for (String city : cityNames) {
         if (cityName.equals(city)) {
            return true;
         }
      }
      return false;
   }
}
_

このシンプルなENUMには、利用可能な自動車と都市が含まれています。これまでのところ、本番コードロジックは次のように機能します。

  1. 都市名(ベルリン)を受け取ります。
  2. ENUM Cars.values()からすべての車を1つずつ調べます。
  3. 提供された都市(ベルリン)で利用可能な車のリストを作成する
  4. 車のリストを返す(AUDI、VW)

新しい要件は、利用可能な車に新しい制約ディーラーを追加する必要があることを示しています。つまり、都市に加えて、車が利用できる(購入できる)ディーラーに基づいてフィルターを適用する必要があります。

ただし、ディーラー制約は1台以上の車に適用できます。たとえば、AUDIのみが都市名とディーラーに基づいてフィルタリングされると想定されています。他のすべての車は、ディーラーが利用可能かどうかに関係なく、(都市に基づいて)常に利用可能です。

単純な解決策があったでしょう。 enumを次のように更新します。

_public enum Cars {
   AUDI("COOL AUTOS", "BERLIN", "FRANKFURT", "HAMBURG", "MUNICH")
   VW("ALL", "BERLIN", "FRANKFURT"),
   MERCEDES("ALL", "HAMBURG", "MUNICH")

   private String dealerName;
   private final String[] cityNames;

   Cars(String dealerName, String... cityNames)  {
      this.dealerName = dealerName;
      this.cityNames = cityNames;
   }
   // rest 
}
_

コードフローは次のようになります。

  1. 都市名とディーラー名を受け取ります。 (ベルリン、ジャンボカーズ)
  2. すべての車を調べ、都市名(ベルリン)に基づいて車のリストを取得します。
  3. ベルリンで利用可能な2台の車(AUDI、VW)のうち、AUDIは、「JUMBO CARS」という制約に一致しない「COOL AUTOS」でのみ利用できます。
  4. リストから削除します。
  5. VWはALL販売店で入手できます。したがって、ディーラー名は無関係です。
  6. VWを戻します。

もう一つの例:

  1. 都市名とディーラー名を受け取ります。 (ベルリン、COOL AUTOS)
  2. すべての車を調べ、都市名(ベルリン)に基づいて車のリストを取得します。
  3. ベルリンで入手可能な2台の車のうち、AUDIは制約に一致する "COOL AUTOS"でのみ使用できます。
  4. VWはすべてのALL販売店で利用できます。
  5. アウディとVWを提供します。

問題:

解決策が気に入らない。それは柔軟性がありません、今日フィルターはディストリビューター、都市と車の関係に基づいて構築される必要があります。明日は別のことになるかもしれません。新しい要件に基づいて、ENUMに密結合を追加しました。

質問

ENUMの機能を強化するために使用できるデザインパターンはありますか?私が好きなだけフィルターを追加できますENUM自体にロジックを構築せずに

機能を強化することは、車、都市、および(新しい)制約の間の関係も意味しますか?

[〜#〜]編集[〜#〜]

  1. 説明とコードを更新して、そのディーラー_!=_メーカーを明確にしました。
  2. これは架空の例です。 1台の車が別の都市で利用可能になるシナリオ、またはより多くの車がENUMの一部になるシナリオは、この問題の問題ではありません。
3
office.aizaz

これに特定のパターンがあるかどうかはわかりませんが、より柔軟な他の方法でこれを処理するさまざまなアプローチがあります。まず、これは非常に厳しく、もろいようです。確かに言うには、ここでのコンテキストについては十分にわかりませんが、自動車メーカーの名前は、ハードコーディングしたいもののようには見えません。列挙型はハードコーディングで取得できるのと同じくらいハードコードされているため、ここではまったく最適なソリューションではない可能性があります。

とはいえ、列挙型の機能の1つは、switchステートメントで機能することです。したがって、次のようなことを簡単に実行できます(Java 12/13を使用していると仮定):

boolean match = switch(brand) {
  case Audi -> distributorName == "COOL AUTOS";
  default -> true;
}

この新しいスイッチ構文では、これを 述語 ラムダとして使用できます。

別のオプションは、ディストリビューター名とそれらがサポートするメーカーをキーにしたマップを作成することです。これは、ハードコーディングする代わりに、データベースまたは他のデータソースにその情報を格納するソリューションに役立ちます。

あなたのコメントに基づいて、あなたはより単純なルールエンジンに向かっているように思えます。あなたが与えた要件については、これはかなり簡単です。

基本的な構造は次のようになります。

private Map<Cars, List<Rules>> rules = new HashMap<>();

void addRule(Cars car, Rule rule) {
  List<Rules> list = rules.get(car);
  if (list == null) {
    list = new ArrayList<>();
    rules.put(car, list);
  }
  list.add(rule);
}

boolean canBuy(Cars car, Location location, Distributor distributor) {
    for (Rule rule : rules.get(car)) {
      if (rule.isRestricted(location, distributor)) return false;
    }

    return true;
}

Ruleは次のように定義されています:

public interface Rule {
   boolean isRestricted(Location location, Distributor distributor);
}

次に、次のようにルールマップを入力できます。

addRule(AUDI, (loc, dist) -> loc == BERLIN && dist == COOLAUTOS);

単純化のためにPerson制約を無視し、ロケーションとディストリビューターも独立したEnumであると想定しています。

繰り返しますが、ここでの列挙型の使用には疑問があります。列挙型は、変更が予期されないものや、コード内の他の変更によってのみ変更されるものに適しています。ブランド、ロケーション、ディストリビューターなどは、特にこのシステムを使用するビジネスが成功した場合、変わる可能性があります。任意のオブジェクトをマップおよび通常のクラスのキーとして使用して、場所やディストリビューターなどを実装できます。ここで概説したこのアプローチは、列挙型に依存していません。

6
JimmyJames