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标签中提出问题。
嗯,我以前从未遇到过在接口中定义常量的需求。 值得一提的是,实现该接口的类不能覆盖常量,而仅相互扩展的类可以覆盖常量。
我相信常数也不错,因为即使我们关注单元可测试性,它们也具有可预测的值。 全局变量是邪恶的,因为任何人都可以更改它,因为它是一个变量并且所有内容都具有作用域,但常量永远不会更改其值,因此,术语"常量"。
好吧,我认为这归结为好与好之间的区别。
虽然在大多数情况下,可以通过实现其他模式(策略或轻量级)来避免使用常量,但是可以说不需要用其他六类来表示一个概念。我认为其归结为需要其他常量的可能性。换句话说,需要扩展接口上的常量所提供的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不会引入任何具体的耦合。话虽如此,我不会仅仅为了向接口添加常量而引入接口。
因此,如果您正在使用接口,并且非常确定自己(或其他任何人)不需要其他值,那么我就不会真正看到接口常数有什么大问题了……最好设计将不包含任何常量或条件,幻数,幻弦或硬编码的任何内容。但是,这会增加开发时间,因为您必须考虑其用途。我的观点是,多数时候绝对值得花额外的时间来构建出色的实体设计。但是有时候确实足够好是可以接受的(需要有经验的开发人员来了解差异),在那种情况下就可以了。
再次,那只是我的看法...
在这种情况下,您会建议用户采取什么其他模式?
@Jacob:我会把它抽象化。根据您的需求,Id可能会构建一个Access类,该类将从数据库表中获取其数据。这样,添加新级别就像插入新行一样容易。另一种选择是建立一个ENUM类集(每个权限角色都有一个类)。然后,您可以在必要时扩展类以提供适当的权限。但是还有其他方法也可以使用
非常扎实和明确的答案! +1
具有公共常量的类不应具有任何方法。它应该只是数据结构或对象,而不是两者。
@OZ_:好吧,这取决于使用情况。如果您要构建ENUM样式类,则应具有与强制执行约束有关的方法(例如SPLEnum)。如果您不这样做,而您只是表示一个可能改变的值的域(以消除幻数和字符串),那么无论是否仅是类,它都将悬而未决。它实际上取决于类和常量的原因。所以不,我不同意带有公共常量的类不应该有方法。我通常会把它们放在一个接口上,但这取决于
"最好的设计不会包含任何常数或条件,幻数或幻弦或硬编码的任何东西。" - 这听起来很不错。您如何避免有条件的决定,这是任何决策过程的核心?
@FrederikKrautwald:您可以避免多态的条件(在大多数情况下):查看此答案以及观看此清洁代码讲座...
@ircmaxell,感谢您的链接。我同意通过重构条件,在大多数情况下可以消除条件。但是,我看不到如何完全避免它们,但这也许只是我缺乏逻辑。
@FrederikKrautwald,您无法完全避免它们。您可以在业务逻辑中完全避免它们。在工厂,制造商和配置逻辑中,它们仍然是必需的,但是实际的"业务工作"可以是完全有条件的……我不是说应该是完全免费的,但是可以。
@ircmaxell,很好。这也是我得出的结论,我不会再为解决如何删除所有和一个问题而费劲。
@ircmaxell我有两个问题:1."最好的设计不会包含任何常量..."那么当拥有10个具有相同字符串的文件时,您将如何管理,您是否要进行单点更改? 2."实际的"业务工作"可以是完全无条件的.."您是否将核心域称为业务工作(实体/价值对象等)。如果是这样,您是否建议使用数组等?如果是这样,我们如何实现规格和验证器?
我认为通常最好将常量(特别是枚举常量)作为接口中的单独类型("类")来处理:
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) { ... }
}
并不是说您仅使用常量,而是使用枚举值或枚举的概念,这些枚举值或枚举被认为是具有特定用法("域"?)的特定类型。