接口常数的优缺点


105

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标签中提出问题。


1
嗯,我以前从来没有遇到过在接口中定义常量的需求。值得一提的是,实现接口的类不能覆盖常量,而仅相互扩展的类可以覆盖常量。
查尔斯

1
我相信常量也不错,因为即使我们关注单元可测试性,它们也具有可预测的值。全局变量是邪恶的,因为任何人都可以更改它,因为它是一个变量并且所有内容都具有作用域,但常量永远不会更改其值,因此,术语“常量”。
mdprotacio

Answers:


135

好吧,我认为这可以归结为之间的区别。

尽管在大多数情况下,可以通过实现其他模式(策略或轻量级)来避免使用常量,但可以说不需要用其他六类来表示一个概念。我认为可以归结为是需要其他常量的可能性。换句话说,需要扩展接口上的常量提供的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定义的,并且在类的生存期内不会更改。

总的来说,我不认为常量和类常量的问题是一个全局问题。我将其视为依赖性问题。这是一个狭窄的区别,但却是一个明确的区别。我将全局问题视为未强制执行的全局变量,因此会产生软的全局依赖关系。但是硬编码的类会创建强制的依赖关系,因此会创建硬的全局依赖关系。因此,两者都是依赖关系。但是我认为全局会更糟糕,因为它没有被强制执行。这就是为什么我不喜欢将类依赖全局依赖放在一起。

如果您编写了代码MyClass::FOO,则会将其硬编码为的实现细节MyClass。这会产生硬耦合,从而使代码的灵活性降低,因此应避免使用。但是,存在接口以完全允许这种类型的耦合。因此MyInterface::FOO不引入任何具体的耦合。话虽如此,我不会仅仅为了向接口添加常量而引入接口。

因此,如果您正在使用接口,并且非常确定自己(或其他任何人)不需要其他值,那么我就不会真正看到接口常数有什么大问题了……最好设计将不包含任何常量或条件,幻数,幻弦或硬编码的任何内容。但是,这会增加开发时间,因为您必须考虑其用途。我的观点是,大多数时候绝对值得花额外的时间来构建出色的实体设计。但是有时候确实足够好是可以接受的(需要有经验的开发人员来了解差异),在那种情况下就可以了。

再次,那只是我的看法...


4
在这种情况下,您会建议用户采取什么其他模式?
雅各布

@Jacob:我会把它抽象出来。根据您的需求,我可能会构建一个Access类,该类将从数据库表中获取其数据。这样,添加新级别就像插入新行一样容易。另一种选择是建立一个ENUM类集(每个权限角色都有一个类)。然后,您可以在必要时扩展类以提供适当的权限。但是还有其他一些方法也可以使用
ircmaxell

3
非常扎实和明确的答案!+1
体面的达布勒

1
具有公共常量的类不应具有任何方法。它应该仅是数据结构或仅对象-不能两者都选。
OZ_ 2011年

2
@FrederikKrautwald:您可以避免使用多态的条件(在大多数情况下):请查看此答案以及观看此清洁代码讲座 ……
ircmaxell,2013年

10

我认为通常最好将常量(特别是枚举常量)作为与接口分开的类型(“类”)来处理:

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) { ... }
}

并不是说您只使用常量,而是使用枚举值或枚举的概念,这些枚举值或枚举被认为是具有特定用法(“域”?)的一组特定类型。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.