web-dev-qa-db-ja.com

「静的インターフェース」は良い習慣ですか?

最近、静的メソッドをインターフェースに含めるオプションがあることに気づきました。インターフェースの静的フィールドと同じように、興味深い動作があります。これらは継承されません。

実装される実際のインターフェースでそれが役立つかどうかはわかりません。ただし、これにより、プログラマーは静的なもののエンベロープにすぎないインターフェースを作成できます。ユーティリティクラスです。

簡単な例は、グローバル定数のエンベロープです。クラスと比較すると、public static finalのボイラープレートが欠落していることが簡単にわかります。

public interface Constants {
    String LOG_MESSAGE_FAILURE = "I took an arrow to the knee.";
    int DEFAULT_TIMEOUT_MS = 30;
}

また、この構成キーの疑似列挙型のように、より複雑なものを作成することもできます。

public interface ConfigKeys {
    static createValues(ConfigKey<?>... values) {
        return Collections.unmodifiableSet(new HashSet(Arrays.asList(values)));
    }

    static ConfigKey<T> key(Class<T> clazz) {
        return new ConfigKey<>(clazz);
    }

    class ConfigKey<T> {
        private final Class<T> type;
        private ConfigKey(Class<T> type) {
            this.type = type;
        }
        private Class<T> getType() {
            return type;
        }
    }
}

import static ConfigKeys.*;
public interface MyAppConfigKeys {
    ConfigKey<Boolean> TEST_MODE = key(Boolean.class);
    ConfigKey<String> COMPANY_NAME = key(String.class);

    Set<ConfigKey<?>> VALUES = createValues(TEST_MODE, COMPANY_VALUE);

    static values() {
        return VALUES;
    }
}

この方法でユーティリティ「クラス」を作成することもできます。ただし、ユーティリティでは、プライベートまたは保護されたヘルパーメソッドを使用すると便利なことがよくあります。これは、クラスでは不可能です。

私はそれを素晴らしい新機能だと思います。特に、静的メンバーが継承されないという事実は、インターフェースのみに導入された興味深い概念です。

それは良い習慣だと思いますか。コードスタイルとベストプラクティスは公理的ではなく、意見の余地はありますが、意見を支持する正当な理由は通常あると思います。

この2つのパターンを使用する(しない)理由にもっと興味があります。


これらのインターフェースを実装するつもりはないことに注意してください。それらは静的コンテンツのエンベロープにすぎません。定数またはメソッドのみを使用し、場合によっては静的インポートを使用するつもりです。

13
Vlasec

簡単な例は、グローバル定数のエンベロープです。

定数は多くの場合、単なる実装の詳細であるため、インターフェイスに定数を置くことはConstant Interface Antipatternと呼ばれます。その他の欠点は、Wikipediaの記事 定数インターフェイス にまとめられています。

この方法でユーティリティ「クラス」を作成することもできます。

確かに Java doc は、静的メソッドを使用すると、ヘルパーメソッドを整理しやすくすることができます(たとえば、デフォルトメソッドからヘルパーメソッドを呼び出す場合など)。

1
Markus Pscheidt

質問で述べたように、インターフェースは実装(またはインスタンス化)するためのものではありません。そのため、プライベートコンストラクターを持つクラスと比較できます。

  • super()を呼び出さずにクラスを拡張することはできませんが、インターフェースは「実装」できます
  • このクラスはリフレクションなしではインスタンス化できませんが、インターフェースは次のように単純な匿名クラスとして簡単にインスタンス化できます:new MyInterface() {};

この比較は、より多くの制御を可能にするため、クラスを支持します。とは言っても、クラスは静的なものを保持することを意図したものではなく、インスタンスを持つことが期待されます。まあ、完璧なオプションはありません。

「静的インターフェース」からの継承については奇妙なこともあります。

  • 「実装」クラスはフィールドを継承しますが、静的メソッドは継承しません
  • 拡張インターフェースは静的メンバーをまったく継承しません
  • (クラスを拡張するクラスはすべての非プライベートメンバーを継承しますが、インターフェイスによって拡張できないため、混乱の余地が少なくなります)

これらは、それを悪いパターンと見なし、これらの場合に静的クラスを使い続ける理由になるかもしれません。ただし、構文糖にハマっている人にとっては、マイナス面があってもなお魅力的かもしれません。

1
Vlasec

@MarkusPscheidtとして、このアイデアは本質的に 定数インターフェース アンチパターンの拡張です。このアプローチは当初、定数の単一のセットが多くの異なるクラスによって継承されることを可能にするために広く使用されていました。これがアンチパターンと見なされなくなった理由は、実際のプロジェクトでこのアプローチを使用して発生した問題が原因でした。これらの問題は定数にのみ関係すると考える理由はないようです。

おそらくもっと重要なことですが、これを行う必要は本当にありません。代わりに static imports を使用し、何をどこからインポートするかを明示してください。

0
JimmyJames

新しい機能を適切に使用する方法に興味がある場合は、JDKのコードを見てください。インターフェースのデフォルトのメソッドは非常に広く使用されており、簡単に見つけることができますが、インターフェースの静的メソッドは非常に珍しいようです。いくつかの例にはJava.time.chrono.ChronologyJava.util.ComparatorJava.util.Map、およびJava.util.functionおよびJava.util.stream

私が見た例のほとんどは、非常に一般的である可能性が高いこれらのAPIの使用を含みます。たとえば、時間API内の異なるタイプ間の変換、たとえば、関数またはコレクション。私の要点:柔軟である必要があるAPIの部分があっても、少数のデフォルトでユースケースの約80%をカバーできる場合は、インターフェースで静的メソッドを使用することを検討してください。この機能がJDKで使用される頻度は低いと思われるので、私は自分のコードでこの機能を使用する場合は非常に保守的です。

あなたの例は、私がJDKで見たもののようには見えません。これらの特定のケースでは、ほぼ確実に、目的のインターフェイスを実装するenumを作成する必要があります。インターフェースは一連の動作を記述し、実際にはその実装のコレクションを維持するビジネスはありません。

0
ngreen