web-dev-qa-db-ja.com

インターフェイス定数の長所と短所

PHPインターフェイスでは、インターフェイスで定数を定義できます。

interface FooBar
{
    const FOO = 1;
    const BAR = 2;
}
echo FooBar::FOO; // 1

実装クラスでは、これらの定数が自動的に使用可能になります。

class MyFooBar implement FooBar
{
}
echo MyFooBar::FOO; // 1

これに対する私自身の考えは、 グローバルは悪であるということ です。しかし、インターフェイス定数にも同じことが当てはまるのでしょうか。 インターフェースに対するコーディングは一般的に良い習慣と見なされているので、クラスコンテキストの外で使用するのに受け入れられる唯一の定数はインターフェース定数の使用ですか?

私はあなたの個人的な意見と、インターフェイス定数を使用するかどうかに興味がありますが、主にあなたの答えで客観的な理由を探しています。これをアンケートタイプの質問にしたくない。インターフェイス定数を使用すると、保守性にどのような影響があるのか​​興味があります。カップリング。または単体テスト。 [〜#〜] solid [〜#〜] PHPとどのように関係していますか? PHPのグッドプラクティスと見なされるコーディング原則に違反しますか?あなたはアイデアを得る...

注:Javaの同様の質問 が存在する理由がいくつかあります。悪い習慣ですが、JavaはPHPではないので、PHPタグ内で再度尋ねるのが正当であると感じました。

101
Gordon

まあ、それはgoodgood十分

ほとんどの場合、他のパターン(戦略またはフライウェイト)を実装することで定数の使用を回避できますが、概念を表すために他の6つのクラスを必要としないと言われることがあります。要するに、他の定数が必要になる可能性があると思います。つまり、インターフェイスの定数によって提供されるENUMを拡張する必要があります。拡張する必要があると予測できる場合は、より形式的なパターンを使用してください。そうでない場合は、それで十分かもしれません(それで十分であるため、記述およびテストするコードが少なくなります)。十分な使用例と不適切な使用例を次に示します。

悪い:

interface User {
    const TYPE_ADMINISTRATOR = 1;
    const TYPE_USER          = 2;
    const TYPE_GUEST         = 3;
}

十分に良い:

interface HTTPRequest_1_1 {
    const TYPE_CONNECT = 'connect';
    const TYPE_DELETE  = 'delete';
    const TYPE_GET     = 'get';
    const TYPE_HEAD    = 'head';
    const TYPE_OPTIONS = 'options';
    const TYPE_POST    = 'post';
    const TYPE_PUT     = 'put';

    public function getType();
}

さて、これらの例を選んだ理由は簡単です。 Userインターフェイスは、ユーザータイプの列挙を定義しています。これは時間とともに拡大する可能性が非常に高く、別のパターンにより適しています。しかし HTTPRequest_1_1はまともなユースケースです。列挙型はRFC2616で定義されており、クラスの存続期間中は変更されないためです。

一般に、定数とクラス定数の問題はglobalの問題とは見なされません。依存関係の問題だと思います。それは狭い区別ですが、明確な違いです。私は、global問題が適用されていないグローバル変数のように見えるため、ソフトグローバル依存関係を作成します。ただし、ハードコーディングされたクラスは強制的な依存関係を作成し、そのためハードグローバルな依存関係を作成します。両方が依存関係です。しかし、私はglobalが強制されていないため、はるかに悪いと考えています...だから私はクラスの依存関係と同じバナーの下にグローバル依存関係...

MyClass::FOOMyClassの実装の詳細にハードコーディングされています。これにより、ハードカップリングが作成され、コードの柔軟性が低下するため、避ける必要があります。ただし、まさにこのタイプの結合を許可するためのインターフェースが存在します。したがって、MyInterface::FOOは、具体的な結合を導入しません。そうは言っても、定数を追加するだけのインターフェースは導入しません。

あなたがインターフェースを使用していて、あなたがveryであるなら、あなた(またはそのことについて他の誰か)が追加の値を必要としないことを確認してください、そして、インターフェイス定数...最適な設計には、定数や条件、マジックナンバー、マジックストリング、ハードコーディングされたものは含まれません。ただし、使用を検討する必要があるため、開発にさらに時間がかかります。私の見方では、たいていの場合、時間をかけてしっかりとした優れたデザインを構築する価値があります。しかし、十分に良いが実際に許容される場合があり(その違いを理解するには経験豊富な開発者が必要です)、それらの場合は問題ありません。

繰り返しますが、それは私の見解です...

132
ircmaxell

通常、定数、特別に列挙された定数を、インターフェイスとは別の型(「クラス」)として処理する方が良いと思います。

define(TYPE_CONNECT, 'connect');
define(TYPE_DELETE , 'delete');
define(TYPE_GET    , 'get');
define(TYPE_HEAD   , 'head');
define(TYPE_OPTIONS, 'options');
define(TYPE_POST   , 'post');
define(TYPE_PUT    , 'put');

interface IFoo
{
  function /* int */ readSomething();
  function /* void */ ExecuteSomething(/* int */ param);
}

class CBar implements IFoo
{
  function /* int */ readSomething() { ...}
  function /* void */ ExecuteSomething(/* int */ param) { ... }
}

または、クラスを名前空間として使用する場合:

class TypeHTTP_Enums
{
  const TYPE_CONNECT = 'connect';
  const TYPE_DELETE  = 'delete';
  const TYPE_GET     = 'get';
  const TYPE_HEAD    = 'head';
  const TYPE_OPTIONS = 'options';
  const TYPE_POST    = 'post';
  const TYPE_PUT     = 'put';
}

interface IFoo
{
  function /* int */ readSomething();
  function /* void */ ExecuteSomething(/* int */ param);
}

class CBar implements IFoo
{
  function /* int */ readSomething() { ...}
  function /* void */ ExecuteSomething(/* int */ param) { ... }
}

定数だけを使用しているのではなく、列挙値または列挙の概念を使用しています。これは、制限された値のセットが、特定の用途(「ドメイン」?)で特定のタイプと見なされます

10
umlcat