インターフェイスを実装し、他のクラスを実装しないクラスでのみ使用可能なメソッドを呼び出すより良い方法はどれですか?


11

基本的に、特定の条件が与えられたときにさまざまなアクションを実行する必要があります。既存のコードはこのように書かれています

基本インターフェース

// DoSomething.java
interface DoSomething {

   void letDoIt(String info);
}

最初のワーカークラスの実装

class DoItThisWay implements DoSomething {
  ...
}

2番目のワーカークラスの実装

class DoItThatWay implements DoSomething {
   ...
}

メインクラス

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          worker = new DoItThisWay();
        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

このコードは問題なく動作し、理解しやすいです。

ここで、新しい要件のために、にのみ意味のある新しい情報を渡す必要がありDoItThisWayます。

私の質問は、次のコーディングスタイルがこの要件を処理するのに適しているかどうかです。

新しいクラス変数とメソッドを使用する

// Use new class variable and method

class DoItThisWay implements DoSomething {
  private int quality;
  DoSomething() {
    quality = 0;
  }

  public void setQuality(int quality) {
    this.quality = quality;
  };

 public void letDoIt(String info) {
   if (quality > 50) { // make use of the new information
     ...
   } else {
     ...
   }
 } ;

}

このようにすると、呼び出し元に対応する変更を加える必要があります。

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          int quality = obtainQualityInfo();
          DoItThisWay tmp = new DoItThisWay();
          tmp.setQuality(quality)
          worker = tmp;

        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

それは良いコーディングスタイルですか?それとも私はそれをキャストすることができます

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          int quality = obtainQualityInfo();
          worker = new DoItThisWay();
          ((DoItThisWay) worker).setQuality(quality)
        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

19
なぜqualityコンストラクタに渡さないのDoItThisWayですか?
デビッドアルノ

私はおそらくのパフォーマンス上の理由のために建設ので...私の質問を修正する必要があるDoItThisWayDoItThatWayのコンストラクタに一度行われますMainMainは長生きのクラスでdoingItあり、何度も何度も呼ばれています。
アンソニーコング

はい、その場合は質問を更新することをお勧めします。
デビッドアルノ

オブジェクトのsetQuality存続期間中にメソッドが複数回呼び出されると言うつもりDoItThisWayですか?
jpmc26

1
提示した2つのバージョンに関連する違いはありません。論理的な観点から、彼らは同じことをします。
セバスチャンレッド

回答:


6

qualityletDoIt()呼び出しと一緒に設定する必要があると仮定していますDoItThisWay()

ここで発生している問題は次のとおりです。一時的なカップリングを導入しています(つまり、?を呼び出すsetQuality()前に呼び出しを忘れるとどうなりますか?)。そして、の実装とは、持っている(1つのニーズを発散していますletDoIt()DoItThisWayDoItThisWayDoItThatWaysetQuality()と呼ばれ、他のではありません)。

これは今のところ問題を引き起こさないかもしれませんが、最終的にあなたを悩ませるように戻ってくるかもしれません。letDoIt()品質情報がinfoあなたが通過するものの一部である必要があるかどうかをもう一度見て検討する価値があるかもしれません。しかし、これは詳細に依存します。


15

それは良いコーディングスタイルですか?

私の観点からは、どちらのバージョンもそうではありません。最初の呼び出しすることsetQualityの前にletDoIt呼び出すことができることで、時間のカップリング。あなたはDoItThisWay、の派生物として見ることにこだわっていますDoSomethingが、それは(少なくとも機能的には)そうではなく、むしろ

interface DoSomethingWithQuality {
   void letDoIt(String info, int quality);
}

これはMainむしろ次のようなものになります

class Main {
    // omitting the creation
    private DoSomething doSomething;
    private DoSomethingWithQuality doSomethingWithQuality;

    public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
            int quality = obtainQualityInfo();
            doSomethingWithQuality.letDoIt(info, quality);
        } else {
            doSomething.letDoIt(info);
        }
    }
}

一方、パラメータをクラスに直接渡し(これが可能であると仮定)、どちらを使用するかを決定することをファクトリに委任できます(これにより、インスタンスが再び交換可能になり、両方を派生させることができますDoSomething)。これは次のようにMainなります

class Main {
    private DoSomethingFactory doSomethingFactory;

     public doingIt(String info) {
         int quality = obtainQualityInfo();
         DoSomething doSomethingWorker = doSomethingFactory.Create(info, quality);
         doSomethingWorker.letDoIt();
     }
}

あなたがそれを書いたことを知っています

パフォーマンス上の理由から、DoItThisWayとDoItThatWayの構築はMainのコンストラクターで1回行われるため

ただし、ファクトリで作成するのにコストがかかる部分をキャッシュし、コンストラクタに渡すこともできます。


ああ、答えをタイプしている間、あなたは私をスパイしていますか?私たちはそのような同様のポイントを作成します(ただし、あなたのポイントはより包括的で雄弁です);)
CharonX

1
@DocBrownええ、それは議論の余地がありますが、DoItThisWay呼び出す前に品質を設定する必要があるため、letDoIt動作が異なります。私はDoSomethingすべてのデリバティブに対して同じ方法で動作することを期待しています(以前は品質を設定していません)。これは理にかなっていますか?もちろんsetQuality、ファクトリメソッドからを呼び出した場合、クライアントは品質を設定する必要があるかどうかについてわからないため、これは一種のぼやけです。
ポールケルシャー

2
@DocBrownの契約letDoIt()は(追加情報がない場合)であると想定しますString infosetQuality()事前に呼び出されることを要求すると、の呼び出しの前提条件が強化され、letDoIt()LSP違反になります。
CharonX

2
@CharonX:たとえば、「letDoIt」が例外をスローしないことを意味する契約があり、「setQuality」の呼び出しを忘れると例外がスローされる場合letDoIt、そのような実装はLSPに違反することに同意します。しかし、それらは非常に人為的な仮定のように見えます。
Doc Brown

1
これは良い答えです。私が追加する唯一のことは、コードベースにとってどれほど悲惨な時間的結合が悪いかについてのコメントです。一見分断されたコンポーネント間の隠れた契約です。通常の結合では、少なくとも明示的で簡単に識別できます。これを行うことは、基本的にピットを掘り、葉で覆うことです。
ジミージェームズ

10

qualityコンストラクタに渡すことができないと仮定し、への呼び出しsetQualityが必要。

現在、次のようなコードスニペット

    int quality = obtainQualityInfo();
    worker = new DoItThisWay();
    ((DoItThisWay) worker).setQuality(quality);

あまりにも多くの考えを投資するには小さすぎます。私見それは少しugいように見えますが、理解するのは本当に難しくありません。

このようなコードスニペットが大きくなり、リファクタリングのために次のような結果になると問題が発生します

    int quality = obtainQualityInfo();
    worker = CreateAWorkerForThisInfo();
    ((DoItThisWay) worker).setQuality(quality);

現在、そのコードがまだ正しいかどうかはすぐにはわかりません。コンパイラーはそれを知らせません。したがって、正しい型の一時変数を導入し、キャストを回避することは、実際の追加作業なしで、もう少し型安全です。

ただし、実際にtmpはより良い名前を付けます。

      int quality = obtainQualityInfo();
      DoItThisWay workerThisWay = new DoItThisWay();
      workerThisWay.setQuality(quality)
      worker = workerThisWay;

このような命名は、間違ったコードを間違って見えるようにするのに役立ちます


tmpは「workerThatUsesQuality」のような名前を付けた方がよい場合があります
user949300

1
@ user949300:それは良い考えではない私見:DoItThisWayより特定のパラメータを取得すると仮定-あなたが提案することによって、名前は毎回変更する必要があります。どのメソッド名を使用できるかではなく、オブジェクトタイプを明確にする変数または変数を使用することをお勧めします。
ドックブラウン

明確にするために、クラスの名前ではなく、変数の名前になります。クラス名にすべての機能を含めるのは良くない考えであることに同意します。だから私はそれworkerThisWayがのようなものになることを提案していますusingQuality。その小さなコードスニペットでは、より具体的にする方が安全です。(また、「usingQuality」よりもドメイン内に優れたフレーズがある場合は、それを使用します。
user94930019年

2

ランタイムデータに基づいて初期化が異なる一般的なインターフェイスは、通常、ファクトリパターンに適しています。

DoThingsFactory factory = new DoThingsFactory(thingThatProvidesQuality);
DoSomething doSomething = factory.getDoer(info);

doSomething.letDoIt();

DoThingsFactoryはgetDoer(info)、DoSomethingインターフェイスにキャストされた具体的なDoThingsWithQualityオブジェクトを返す前に、メソッド内で品質情報を取得および設定することを心配できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.