2012-10-21 17 views
18

例2從PHP手冊http://php.net/manual/en/language.oop5.traits.php狀態PHP trait:有沒有一種合適的方法來確保使用特徵的類擴展了一個包含特定方法的超類?

<?php 
class Base { 
    public function sayHello() { 
     echo 'Hello '; 
    } 
} 

trait SayWorld { 
    public function sayHello() { 
     parent::sayHello(); 
     echo 'World!'; 
    } 
} 

class MyHelloWorld extends Base { 
    use SayWorld; 
} 

$o = new MyHelloWorld(); 
$o->sayHello(); 
?> 

這是正確的代碼,但它不是安全在這方面使用parent::。比方說,我寫我自己的「Hello World」類沒有繼承任何其他類:直到我稱之爲sayHello()方法

<?php 
class MyOwnHelloWorld 
{ 
    use SayWorld; 
} 
?> 

此代碼不會產生任何錯誤。這不好。

另一方面,如果性狀需要使用某種方法我可以寫這個方法爲摘要,這是很好的,因爲它可以確保特徵在編譯時正確使用。但是,這並不適用於父類:

<?php 
trait SayWorld 
{ 
    public function sayHelloWorld() 
    { 
     $this->sayHello(); 
     echo 'World!'; 
    } 

    public abstract function sayHello(); // compile-time safety 

} 

所以我的問題是:有沒有一種方法,以確保(在編譯時,而不是在運行時),它採用某些性狀將有parent::sayHello()方法這門課嗎?

+0

你可以做的是添加一個抽象方法來強制使用該特徵的類具有所需的方法。這樣它變得獨立於類層次結構(特質背後的想法:P) – Jarry

回答

4

不,沒有。實際上,這個例子非常糟糕,因爲引入特徵的目的是在不依賴於繼承的情況下爲許多類引入相同的功能,並且使用不僅需要類具有父類,而且還應該具有特定的方法。

在附註中,parent調用在編譯時沒有被檢查,你可以定義一個簡單的類,它不會在其方法中使用父調用來擴展任何東西,它會一直工作,直到調用其中一個方法。

+0

感謝您的支持。我知道簡單的類在編譯時不檢查父調用,但它仍然是一個正確的代碼。我這樣聰明的IDE可以計算出父代調用,這意味着當我從PHP切換到強類型語言時,它仍然是正確的代碼。這允許我在晚上睡覺。但是當涉及到多重繼承時,我不知道有任何其他語言不能做到這一點。 Scala中的特徵概念允許以兩種方式進行繼承。 C++和Python類可以繼承多個「父母」。 –

+0

所以我想我正在尋找解決PHP限制的方法,這將使我的代碼更安全一些。 –

0

我覺得有一種方法,完全沒有特點:

class BaseClass 
{ 
    public function sayHello() { 
      echo 'Hello '; 
    } 
} 

class SayWorld 
{ 
    protected $parent = null; 

    function __construct(BaseClass $base) { 
     $this->parent = $base; 
    } 

    public function sayHelloWorld() 
    { 
     $this->parent->sayHello(); 
     echo 'World!'; 
    } 
} 

class MyHelloWorld extends Base { 
    protected $SayWorld = null; 

    function __construct() { 
     $this->SayWorld = new SayWorld($this); 
    } 

    public function __call (string $name , array $arguments) { 
     if(method_exists($this->SayWorld, $name)) { 
      $this->SayWorld->$name(); 
     } 
    } 
} 
2

您可以檢查$這個延伸的具體類或實現一個特定的接口:

interface SayHelloInterface { 
    public function sayHello(); 
} 

trait SayWorldTrait { 
    public function sayHello() { 
     if (!in_array('SayHello', class_parents($this))) { 
      throw new \LogicException('SayWorldTrait may be used only in classes that extends SayHello.'); 
     } 
     if (!$this instanceof SayHelloInterface) { 
      throw new \LogicException('SayWorldTrait may be used only in classes that implements SayHelloInterface.'); 
     } 
     parent::sayHello(); 
     echo 'World!'; 
    } 
} 

class SayHello { 
    public function sayHello() { 
     echo 'Hello '; 
    } 
} 

class First extends SayHello { 
    use SayWorldTrait; 
} 

class Second implements SayHelloInterface { 
    use SayWorldTrait; 
} 

try { 
    $test = new First(); 
    $test->sayHello(); // throws logic exception because the First class does not implements SayHelloInterface 
} catch(\Exception $e) { 
    echo $e->getMessage(); 
} 

try { 
    $test = new Second(); 
    $test->sayHello(); // throws logic exception because the Second class does not extends SayHello 
} catch(\Exception $e) { 
    echo $e->getMessage(); 
} 
1

的PHP編譯階段只是創建字節碼。其他一切都是在運行時完成的,包括多態決策。因此,像下面的代碼編譯:

class A {} 
class B extends A { 
    public function __construct() { 
     parent::__construct(); 
    } 
} 

但炸燬當run

$b = new B; 

因此,你嚴格不能有parent在編譯時檢查。你可以做的最好的事情是儘可能早地把它推遲到運行時間。如其他答案所示,可以在0123.的特質方法中進行此操作。當我知道特質方法需要特定合約時,我個人更喜歡使用類型提示。

所有這一切都歸結爲特徵的根本目的:編譯時複製和粘貼。而已。性狀對合同一無所知。對多態性一無所知。他們只是提供一個重用機制。當與界面相結合時,它們非常強大,但有些冗長。

事實上,first academic discussion of traits戳穿的想法,性狀意思是「純粹的」,因爲它們具有對象的周圍沒有任何知識:

性狀是本質上是一羣純粹的方法是充當類的構建塊,並且是代碼重用的基本單元。在這個模型中,類是通過指定將特徵連接在一起並訪問必要狀態的粘合代碼從一組特徵組成的。

"If Traits Weren't Evil, They'd Be Funny"很好地總結了點,陷阱和選項。我不認同作者的特質:我使用它們的時候是有道理的,並且總是與interface配對。 PHP文檔中的示例鼓勵不良行爲是不幸的。

+1

從我問這個問題開始,我實際上停止了使用特質。我儘可能地重寫舊代碼來放棄特性。你可能會說我長大了。他們有時是有用的,但只在極少數情況下。 –

相關問題