给 Java 开发人员的提示:PHP 中不使用 'final' 关键字来声明类常量。我们使用 'const' 关键字。
https://php.net/manual/en/language.oop5.constants.php
final 关键字阻止子类重写方法、属性或常量,方法是在定义前添加 final
关键字。如果类本身被定义为 final,则它不能被扩展。
示例 #1 final 方法示例
<?php
class BaseClass {
public function test() {
echo "BaseClass::test() called\n";
}
final public function moreTesting() {
echo "BaseClass::moreTesting() called\n";
}
}
class ChildClass extends BaseClass {
public function moreTesting() {
echo "ChildClass::moreTesting() called\n";
}
}
// 将导致致命错误:无法重写最终方法 BaseClass::moreTesting()
?>
示例 #2 final 类示例
<?php
final class BaseClass {
public function test() {
echo "BaseClass::test() called\n";
}
// 由于类已经是 final,final 关键字是多余的
final public function moreTesting() {
echo "BaseClass::moreTesting() called\n";
}
}
class ChildClass extends BaseClass {
}
// 将导致致命错误:类 ChildClass 不能继承自 final 类 (BaseClass)
?>
示例 #3 自 PHP 8.4.0 起的 final 属性示例
<?php
class BaseClass {
final protected string $test;
}
class ChildClass extends BaseClass {
public string $test;
}
// 将导致致命错误:无法重写 final 属性 BaseClass::$test
?>
示例 #4 自 PHP 8.1.0 起的 final 常量示例
<?php
class Foo
{
final public const X = "foo";
}
class Bar extends Foo
{
public const X = "bar";
}
// 致命错误:Bar::X 无法重写最终常量 Foo::X
?>
注意: 从 PHP 8.0.0 开始,私有方法不能声明为 final,构造函数 除外。
注意:声明为
private(set)
的属性隐式为final
。
给 Java 开发人员的提示:PHP 中不使用 'final' 关键字来声明类常量。我们使用 'const' 关键字。
https://php.net/manual/en/language.oop5.constants.php
请注意,即使在父类中将 final 方法定义为私有,也不能重写它们。
因此,以下示例
<?php
class parentClass {
final private function someMethod() { }
}
class childClass extends parentClass {
private function someMethod() { }
}
?>
将报错 "Fatal error: Cannot override final method parentClass::someMethod() in ***.php on line 7"
这种行为看起来有点出乎意料,因为在子类中我们无法知道父类中存在哪些私有方法,反之亦然。
因此,请记住,如果您定义了一个私有的 final 方法,则不能在子类中放置同名方法。
@thomas at somewhere dot com
'final' 关键字非常有用。继承也很有用,但可能会被滥用,并在大型应用程序中成为问题。如果您遇到希望扩展的最终类或方法,请改写装饰器。
<?php
final class Foo
{
public method doFoo()
{
// 做一些有用的事情并返回结果
}
}
final class FooDecorator
{
private $foo;
public function __construct(Foo $foo)
{
$this->foo = $foo;
}
public function doFoo()
{
$result = $this->foo->doFoo();
// ... 自定义结果 ...
return $result;
}
}
?>
<?php
class parentClass {
public function someMethod() { }
}
class childClass extends parentClass {
public final function someMethod() { } // 重写父函数
}
$class = new childClass;
$class->someMethod(); // 调用子类中的重写函数
?>
您可以使用 final 方法替换类常量。这样做的原因是,您无法隔离地单元测试另一个类中使用的类常量,因为您无法模拟常量。final 方法允许您拥有与常量相同的功能,同时保持代码的松散耦合。
紧密耦合示例(不应使用常量)
<?php
接口 FooInterface
{
}
类 Foo 实现 FooInterface
{
常量 BAR = 1;
公有函数 __construct()
{
}
}
接口 BazInterface
{
公有函数 getFooBar();
}
// 此类无法单独进行单元测试,因为必须加载实际类 Foo 才能获取 Foo::BAR 的值
类 Baz 实现 BazInterface
{
私有 $foo;
公有函数 __construct(FooInterface $foo)
{
$this->foo = $foo;
}
公有函数 getFooBar()
{
返回 Foo::BAR;
}
}
$foo = new Foo();
$baz = new Baz($foo);
$bar = $baz->getFooBar();
?>
松耦合示例(消除了常量使用)
<?php
接口 FooInterface
{
公有函数 bar();
}
类 Foo 实现 FooInterface
{
公有函数 __construct()
{
}
最终公有函数 bar()
{
返回 1;
}
}
接口 BazInterface
{
公有函数 getFooBar();
}
// 此类可以单独进行单元测试,因为通过模拟 FooInterface 并调用最终的 bar 方法,不需要加载类 Foo。
类 Baz 实现 BazInterface
{
私有 $foo;
公有函数 __construct(FooInterface $foo)
{
$this->foo = $foo;
}
公有函数 getFooBar()
{
返回 $this->foo->bar();
}
}
$foo = new Foo();
$baz = new Baz($foo);
$bar = $baz->getFooBar();
?>
我认为值得了解
<?php
类 BaseClass
{
受保护的静态 $var = '我属于 BaseClass';
公有静态函数 test()
{
echo '<hr>'.
'我是 `'.__METHOD__.'()` 并且这是我的变量: `'.self::$var.'`<br>';
}
公有静态函数 changeVar($val)
{
self::$var = $val;
echo '<hr>'.
'我是 `'.__METHOD__.'()` 并且我刚刚将我的 $var 更改为: `'.self::$var.'`<br>';
}
最终公有静态函数 dontCopyMe($val)
{
self::$var = $val;
echo '<hr>'.
'我是 `'.__METHOD__.'()` 并且我刚刚将我的 $var 更改为: `'.self::$var.'`<br>';
}
}
类 ChildClass 扩展 BaseClass
{
受保护的静态 $var = '我属于 ChildClass';
公有静态函数 test()
{
echo '<hr>'.
'我是 `'.__METHOD__.'()` 并且这是我的变量: `'.self::$var.'`<br>'.
'并且这是我的父变量: `'.parent::$var.'`';
}
公有静态函数 changeVar($val)
{
self::$var = $val;
echo '<hr>'.
'我是 `'.__METHOD__.'()` 并且我刚刚将我的 $var 更改为: `'.self::$var.'`<br>'.
'但是父 $var 仍然是: `'.parent::$var.'`';
}
公有静态函数 dontCopyMe($val) // 致命错误: 无法覆盖最终方法 BaseClass::dontCopyMe() in ...
{
self::$var = $val;
echo '<hr>'.
'我是 `'.__METHOD__.'()` 并且我刚刚将我的 $var 更改为: `'.self::$var.'`<br>';
}
}
BaseClass::test(); // 我是 `BaseClass::test()` 并且这是我的变量: `我属于 BaseClass`
ChildClass::test(); // 我是 `ChildClass::test()` 并且这是我的变量: `我属于 ChildClass`
// 并且这是我的父变量: `我属于 BaseClass`
ChildClass::changeVar('一些新的东西'); // 我是 `ChildClass::changeVar()` 并且我刚刚将我的 $var 更改为: `一些新的东西`
// 但是父 $var 仍然是: `我属于 BaseClass`
BaseClass::changeVar('一些不同的东西'); // 我是 `BaseClass::changeVar()` 并且我刚刚将我的 $var 更改为: `一些不同的东西`
BaseClass::dontCopyMe('一段文本'); // 我是 `BaseClass::dontCopyMe()` 并且我刚刚将我的 $var 更改为: `一段文本`
ChildClass::dontCopyMe('一段文本'); // 致命错误: 无法覆盖最终方法 BaseClass::dontCopyMe() in ...
?>
final 关键字的使用就像在 Java 中一样。
在 Java 中,final 有三种用途:
1) 防止类继承
2) 防止方法重写或在子类中重新定义方法
方法
3) 并声明常量
但是 PHP 中似乎缺少第三点。
我想,因为我是一名 Java 开发人员,目前正在学习 PHP。
FINAL 的行为并不像你想象的那么严重。一个小例子
<?php
类 A {
最终私有函数 method(){}
}
类 B 扩展 A {
私有函数 method(){}
}
?>
通常情况下,你可能会期望发生以下情况:
- final 和 private 关键字不能一起使用导致错误
- 没有错误,因为 private 可见性表示方法/变量/等仅在同一类中可见
但是 PHP 的情况有点奇怪:“无法覆盖最终方法 A::method()”
因此,可以在子类中拒绝方法名!不知道这是否是一个好的行为,但也许对你的目的有用。
当需要特殊的类结构时,最终确定魔法方法可能会有所帮助。
<?php
抽象类 A {
最终公有函数 __construnct(){ echo "A"; }
}
类 B 扩展 A {
公有函数 __construct(){ echo "B"; }
}
$b = new B(); // 输出: PHP 致命错误: 无法覆盖最终方法 a\A::__construct()
?>
“注意:Java 开发者请注意,PHP 中不使用 'final' 关键字来定义类常量。我们使用 'const' 关键字。”
https://php.net/manual/en/language.oop5.constants.php
这或多或少是正确的,尽管事实上 PHP 中的常量(无论是否在类级别定义)只能是标量(int,string 等),而在 Java 中它们可以是纯对象(例如:java.awt.Color.BLACK)。要实现这种类型的常量,唯一可能的解决方案是
<?php
class Bar {...}
class Foo {
public static $FOOBAR;
static function __init() {
static $init = false;
if ($init) throw new Exception('Constants were already initialized');
self::$FOOBAR = new Bar();
$init = true;
}
}
Foo::__init();
?>
也就是说,除非 PHP 自动调用 __init() 方法,否则这可能没有用。
然而,在某些情况下,可以采用另一种方法
<?php
function __autoload($className) {
... require 包含该类的文件 ...
if (interface_exists($className, false)) return;
if (class_exists($className, false)) {
$rc = new ReflectionClass($className);
if (!$rc->hasMethod('__init')) return;
$m = $rc->getMethod('__init');
if (!($m->isStatic() && $m->isPrivate())) {
throw new Exception($className . ' __init() 方法必须是私有静态方法!');
}
$m->invoke(null);
return;
}
throw new Exception('未找到类或接口 ' . $className);
}
?>
这只在每个文件定义一个类时才有效,因为我们可以确保会调用 __autoload() 来加载包含该类的文件。
例如
test2.php
<?php
class B {
public static $X;
private static function __init() {
echo 'B', "\n";
self::$X = array(1, 2);
}
}
class A {
public static $Y;
private static function __init() {
echo 'A', "\n";
self::$Y = array(3, 4);
}
}
?>
test.php
<?php
function __autoload($n) {
if ($n == 'A' || $n == 'B') require 'test2.php';
... 执行我们的 __init() 方法技巧 ...
}
var_dump(B::$X); // 显示 B,然后是 array(2) (1, 2)
var_dump(A::$Y); // 显示 NULL。
?>