給 Java 開發者的注意事項:PHP 中的類別常數不使用 'final' 關鍵字。我們使用 'const' 關鍵字。
https://php.dev.org.tw/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";
}
}
// 造成致命錯誤:無法覆寫 final 方法 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 Final 屬性範例,自 PHP 8.4.0 起
<?php
class BaseClass {
final protected string $test;
}
class ChildClass extends BaseClass {
public string $test;
}
// 造成致命錯誤:無法覆寫 final 屬性 BaseClass::$test
?>
範例 #4 Final 常數範例,自 PHP 8.1.0 起
<?php
class Foo
{
final public const X = "foo";
}
class Bar extends Foo
{
public const X = "bar";
}
// 致命錯誤:Bar::X 無法覆寫 final 常數 Foo::X
?>
注意:自 PHP 8.0.0 起,除了建構子之外,私有方法不可宣告為 final。
注意:宣告為
private(set)
的屬性會隱含地視為final
。
給 Java 開發者的注意事項:PHP 中的類別常數不使用 'final' 關鍵字。我們使用 'const' 關鍵字。
https://php.dev.org.tw/manual/en/language.oop5.constants.php
請注意,即使 final 方法在父類中被定義為 private,您也無法覆寫它們。
因此,以下範例
<?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」而終止執行。
這種行為看起來有點出乎意料,因為在子類別中,我們無法知道父類別中存在哪些 private 方法,反之亦然。
因此,請記住,如果您定義了一個 private 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
interface FooInterface
{
}
class Foo implements FooInterface
{
const BAR = 1;
public function __construct()
{
}
}
interface BazInterface
{
public function getFooBar();
}
// 此類別無法單獨進行單元測試,因為也必須載入實際的 Foo 類別才能取得 Foo::BAR 的值
class Baz implements BazInterface
{
private $foo;
public function __construct(FooInterface $foo)
{
$this->foo = $foo;
}
public function getFooBar()
{
return Foo::BAR;
}
}
$foo = new Foo();
$baz = new Baz($foo);
$bar = $baz->getFooBar();
?>
低耦合示例(消除了常數的使用)
<?php
介面 FooInterface
{
public function bar();
}
類別 Foo 實作 FooInterface
{
public function __construct()
{
}
final public function bar()
{
return 1;
}
}
介面 BazInterface
{
public function getFooBar();
}
// 這個類別可以單獨進行單元測試,因為透過模擬 FooInterface 並呼叫 final 的 bar 方法,不需要載入 Foo 類別。
類別 Baz 實作 BazInterface
{
private $foo;
public function __construct(FooInterface $foo)
{
$this->foo = $foo;
}
public function getFooBar()
{
return $this->foo->bar();
}
}
$foo = new Foo();
$baz = new Baz($foo);
$bar = $baz->getFooBar();
?>
我認為這值得了解
<?php
class BaseClass
{
protected static $var = 'i belong to BaseClass';
public static function test()
{
echo '<hr>'.
'i am `'.__METHOD__.'()` and this is my var: `'.self::$var.'`<br>';
}
public static function changeVar($val)
{
self::$var = $val;
echo '<hr>'.
'i am `'.__METHOD__.'()` and i just changed my $var to: `'.self::$var.'`<br>';
}
final public static function dontCopyMe($val)
{
self::$var = $val;
echo '<hr>'.
'i am `'.__METHOD__.'()` and i just changed my $var to: `'.self::$var.'`<br>';
}
}
class ChildClass extends BaseClass
{
protected static $var = 'i belong to ChildClass';
public static function test()
{
echo '<hr>'.
'i am `'.__METHOD__.'()` and this is my var: `'.self::$var.'`<br>'.
'and this is my parent var: `'.parent::$var.'`';
}
public static function changeVar($val)
{
self::$var = $val;
echo '<hr>'.
'i am `'.__METHOD__.'()` and i just changed my $var to: `'.self::$var.'`<br>'.
'but the parent $var is still: `'.parent::$var.'`';
}
public static function dontCopyMe($val) // Fatal error: Cannot override final method BaseClass::dontCopyMe() in ...
{
self::$var = $val;
echo '<hr>'.
'i am `'.__METHOD__.'()` and i just changed my $var to: `'.self::$var.'`<br>';
}
}
BaseClass::test(); // i am `BaseClass::test()` and this is my var: `i belong to BaseClass`
ChildClass::test(); // i am `ChildClass::test()` and this is my var: `i belong to ChildClass`
// and this is my parent var: `i belong to BaseClass`
ChildClass::changeVar('something new'); // i am `ChildClass::changeVar()` and i just changed my $var to: `something new`
// but the parent $var is still: `i belong to BaseClass`
BaseClass::changeVar('something different'); // i am `BaseClass::changeVar()` and i just changed my $var to: `something different`
BaseClass::dontCopyMe('a text'); // i am `BaseClass::dontCopyMe()` and i just changed my $var to: `a text`
ChildClass::dontCopyMe('a text'); // Fatal error: Cannot override final method BaseClass::dontCopyMe() in ...
?>
final 關鍵字的用法就像在 Java 中一樣
在 Java 中,final 有三種用法
1) 防止類別繼承
2) 防止方法覆寫或在子類別中重新定義方法
3) 宣告常數
但第三點似乎在 PHP 中沒有
我猜,因為我是一個 Java 開發者,目前正在學習 PHP
FINAL 的行為不像你想的那麼嚴格。一個小例子
<?php
類別 A {
final private function method(){}
}
類別 B 繼承 A {
private function method(){}
}
?>
通常你會預期會發生以下情況
- 錯誤:final 和 private 關鍵字不能一起使用
- 沒有錯誤,因為 private 的可見性表示方法/變數/等等只在同一個類別中可見
但 PHP 的情況有點奇怪:「無法覆寫 final 方法 A::method()」
所以可以拒絕子類別中的方法名稱!不知道這是不是一個好的行為,但或許對你的目的有用。
當需要特殊的類別結構時,將魔術方法設為 final 可能會有幫助。
<?php
abstract class A {
final public function __construnct(){ echo "A"; }
}
class B extends A {
public function __construct(){ echo "B"; }
}
$b = new B(); // 輸出:PHP 致命錯誤:無法覆寫最終方法 a\A::__construct()
?>
「給 Java 開發者的注意事項:在 PHP 中,『final』關鍵字不用於類別常數。我們使用『const』關鍵字。」
https://php.dev.org.tw/manual/en/language.oop5.constants.php
無論 PHP 中的常數(無論是否在類別層級定義)都只能是純量值(整數、字串等),而 Java 中的常數可以是純物件(例如:java.awat.Color.BLACK),這點大致上是正確的。 要擁有這種類型常數的唯一可能解法是:
<?php
class Bar {...}
class Foo {
public static $FOOBAR;
static function __init() {
static $init = false;
if ($init) throw new Exception('常數已被初始化');
self::$FOOBAR = new Bar();
$init = true;
}
}
Foo::__init();
?>
話雖如此,除非 PHP 自動呼叫 __init() 方法,否則這可能沒有用。
然而,在某些情況下可以使用的替代方案是:
<?php
function __autoload($className) {
... 載入類別所在的檔案 ...
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。
?>