Tại sao PHP Trait không thể triển khai giao diện?


82

Tôi tự hỏi tại sao PHP Trait (PHP 5.4) không thể triển khai các giao diện.

Cập nhật từ câu trả lời của user1460043 => ... không thể yêu cầu lớp sử dụng nó để triển khai một giao diện cụ thể

Tôi hiểu rằng điều đó có thể là hiển nhiên, bởi vì mọi người có thể nghĩ rằng nếu a Class Ađang sử dụng một Trait Tmà đang triển khai một interface I, hơn là Class Alẽ ra đang triển khai một cách interface Igián tiếp (và điều này không đúng vì Class Acó thể đổi tên các phương thức đặc điểm).

Trong trường hợp của tôi, đặc điểm của tôi là gọi các phương thức từ giao diện mà lớp sử dụng đặc điểm thực hiện.

Đặc điểm trên thực tế là sự triển khai một số phương pháp của giao diện. Vì vậy, tôi muốn "thiết kế" trong mã mà mọi lớp muốn sử dụng đặc điểm của tôi phải triển khai giao diện. Điều đó sẽ cho phép Trait sử dụng các phương thức của lớp được xác định bởi giao diện và đảm bảo rằng chúng đang tồn tại trong lớp.



13
Đó không phải là vấn đề, tôi biết sự khác biệt giữa các đặc điểm và giao diện.
Leto

1
Có lẽ có một lý do kỹ thuật nhưng tôi tự hỏi tại sao bạn lại muốn? Bạn không thể khởi tạo một đặc điểm nên việc nó triển khai một giao diện không mang lại cho bạn bất kỳ lợi ích nào về việc đánh chữ. Nếu bạn muốn điều này, như bạn nói, để buộc các lớp sử dụng đặc điểm để triển khai một giao diện thì bạn sẽ tự hỏi liệu một lớp cơ sở (trừu tượng) có phù hợp hơn không.

Bạn nói đúng, tôi có thể sử dụng các lớp trừu tượng ở khắp mọi nơi nhưng tôi đang cập nhật mã của mình lên Trait, và nó tránh được các vấn đề mà tôi gặp phải với kế thừa đơn giản, đó là lý do tại sao tôi đang sử dụng trait. Vì vậy, có thể trong trường hợp đó là có thể nhưng trong một số trường hợp khác thì không.
Leto

2
Hoặc có thể nói một cách đơn giản hơn: tại sao không có kiểu Traits trong PHP?
nnevala

Câu trả lời:


97

Phiên bản thực sự ngắn đơn giản hơn vì bạn không thể. Đó không phải là cách Traits hoạt động.

Khi bạn viết use SomeTrait;bằng PHP, bạn đang (một cách hiệu quả) yêu cầu trình biên dịch sao chép và dán mã từ Trait vào lớp mà nó đang được sử dụng.

Bởi vì use SomeTrait;bên trong lớp, nó không thể thêm implements SomeInterfacevào lớp, bởi vì cái đó phải ở bên ngoài lớp.

"tại sao không có kiểu Traits trong PHP?"

Bởi vì chúng không thể được khởi tạo. Đặc điểm thực sự chỉ là một cấu trúc ngôn ngữ (yêu cầu trình biên dịch sao chép và dán mã đặc điểm vào lớp này) trái ngược với một đối tượng hoặc kiểu có thể được tham chiếu bởi mã của bạn.

Vì vậy, tôi muốn "thiết kế" trong mã mà mọi lớp muốn sử dụng đặc điểm của tôi phải triển khai giao diện.

Điều đó có thể được thực thi bằng cách sử dụng một lớp trừu tượng cho useđặc điểm và sau đó mở rộng các lớp từ nó.

interface SomeInterface{
    public function someInterfaceFunction();
}

trait SomeTrait {
    function sayHello(){
        echo "Hello my secret is ".static::$secret;
    }
}

abstract class AbstractClass implements SomeInterface{
    use SomeTrait;
}

class TestClass extends AbstractClass {
    static public  $secret = 12345;

    //function someInterfaceFunction(){
        //Trying to instantiate this class without this function uncommented will throw an error
        //Fatal error: Class TestClass contains 1 abstract method and must therefore be 
        //declared abstract or implement the remaining methods (SomeInterface::doSomething)
    //}
}

$test = new TestClass();

$test->sayHello();

Tuy nhiên - nếu bạn cần thực thi rằng bất kỳ lớp nào sử dụng một Đặc điểm đều có một phương thức cụ thể, tôi nghĩ bạn có thể đang sử dụng các đặc điểm mà ngay từ đầu bạn đã phải là các lớp trừu tượng.

Hoặc bạn có logic của bạn sai cách. Bạn muốn yêu cầu các lớp triển khai giao diện có một số chức năng nhất định, không phải là nếu chúng có một số chức năng nhất định thì chúng phải tự khai báo là triển khai giao diện.

Biên tập

Trên thực tế, bạn có thể định nghĩa các hàm trừu tượng bên trong các Đặc điểm để buộc một lớp triển khai phương thức. ví dụ

trait LoggerTrait {

    public function debug($message, array $context = array()) {
        $this->log('debug', $message, $context);
    }

    abstract public function log($level, $message, array $context = array());
}

Tuy nhiên, điều này vẫn không cho phép bạn triển khai giao diện theo đặc điểm và vẫn có vẻ là một thiết kế tồi, vì các giao diện tốt hơn nhiều so với các đặc điểm trong việc xác định hợp đồng mà một lớp cần thực hiện.


2
Làm thế nào bạn đề nghị đặt nó ra sau đó, tôi có một lớp Người, lớp này được trừu tượng hóa thành các lớp con dựa trên Công việc, nhưng nhiều công việc trong số này chia sẻ các tính năng được triển khai tốt nhất với mã chia sẻ (ví dụ: cả thư ký và lập trình viên đều cần typephương thức ). Bạn có thể nghĩ về cách điều này có thể được thực hiện mà không có đặc điểm không?
scragar

@scragar bạn nên hỏi điều đó tại programmers.stackexchange.com nhưng phiên bản ngắn gọn là tôi muốn kết hợp 'Con người' với nhiều 'Công việc' để thành một lớp 'Con người làm việc'.
Danack

1
Một lần nữa. Nếu giao diện xác định một số hợp đồng nhận thức và hợp đồng đó là chung cho hầu hết các triển khai. Nhưng những triển khai đó có loại ba của riêng chúng. Một cái gì đó giống như Lệnh với ContainerAwareInterface. Nhưng Comand có các lĩnh vực sử dụng cụ thể của riêng họ. Vì vậy, tôi cần phải lặp lại chính mình mỗi khi tôi cần Nhận thức về vùng chứa nhưng nếu tôi sử dụng Đặc điểm, tôi không thể xác định hợp đồng riêng của nó cho Giao diện cụ thể. Có lẽ các nhà phát triển cốt lõi nên xem xét các giao diện Go-Type (ví dụ: Structural Typing)?
lazycommit

3
nó thực sự lạ, vì thực sự các đồng nghiệp của tôi và tôi chỉ sử dụng các đặc điểm khi chúng tôi muốn chia sẻ mã được yêu cầu trong một số lớp triển khai một giao diện nhưng đến từ các tổ tiên khác nhau. Cũng không có lời giải thích hợp lý tại sao trình biên dịch có thể thay đổi mã bên trong lớp mà không phải là các giao diện được thực hiện bởi lớp này. ... nó chỉ đơn giản là một tính năng "bị thiếu" ... "bởi vì bạn không thể" giải thích điều này tốt nhất
Summer-Sky

5
Tôi tin rằng các nhà phát triển cốt lõi PHP nên nghiên cứu một chút về Scala, nơi mà các đặc điểm được coi là loại chính thức ... Tôi thấy thật buồn khi PHP dần dần muốn cải thiện hệ thống đánh máy của mình nhưng không tính đến các triển khai hoạt động tốt hiện có
Vincent Pazeller

28

Có một RFC: Các đoạn có giao diện gợi ý sau đây để được thêm vào ngôn ngữ:

trait SearchItem implements SearchItemInterface
{
    ...
}

Các phương thức được yêu cầu bởi giao diện có thể được thực thi bởi đặc điểm, hoặc được khai báo là trừu tượng, trong trường hợp đó, lớp sử dụng đặc điểm sẽ triển khai nó.

Tính năng này hiện không được ngôn ngữ hỗ trợ, nhưng nó đang được xem xét (trạng thái hiện tại của RFC là: Đang thảo luận ).


Tôi cho rằng, nếu được xác nhận, thì mọi người sẽ muốn ngày càng nhiều tính năng từ các lớp bình thường được triển khai thành đặc điểm. Cho đến khi không có sự khác biệt giữa chúng và chúng ta sẽ có một số loại đặc điểm Frankenstein không phân chia mối quan tâm và trách nhiệm một cách hợp lý. Như câu trả lời tốt nhất nhấn mạnh, các đặc điểm được coi là sự tiện lợi trong quá khứ sao chép; không nên vượt qua ranh giới của các lớp học quá nhiều. Chúng tôi muốn một lớp triển khai một giao diện, cho dù việc triển khai đến từ mã trực tiếp hay từ việc sử dụng một đặc điểm; cho phép triển khai các giao diện thành các đặc điểm có thể gây nhầm lẫn và gây hiểu lầm
Kamafeather

Một ứng dụng tuyệt vời của các đặc điểm là cung cấp một giao diện cài đặt mặc định dễ dán. Nếu bạn muốn đảm bảo rằng một đặc điểm hoàn thành một giao diện, sẽ rất tuyệt nếu bạn có trình biên dịch trợ giúp
The Mighty Chris

Đề xuất này không làm cho một lớp sử dụng một đặc điểm sẽ tự động triển khai các giao diện đặc điểm đó (điều đó sẽ không hoạt động vì bạn có thể đổi tên / thay thế các phương thức đặc điểm). Lập luận về độ dốc trơn trượt rằng việc vượt qua điều này bằng cách nào đó sẽ vượt qua các RFC trong tương lai tích cực hơn sẽ không giữ nước
The Mighty Chris

10

[...] để "thiết kế" trong mã mà mọi lớp muốn sử dụng đặc điểm của tôi phải triển khai giao diện. Điều đó sẽ cho phép Trait sử dụng các phương thức của lớp được xác định bởi giao diện và đảm bảo rằng chúng đang tồn tại trong lớp.

Điều này nghe có vẻ rất hợp lý và tôi sẽ không nói rằng thiết kế của bạn có gì sai. Các đặc điểm đã được đề xuất với ý tưởng này, hãy xem điểm thứ hai tại đây:

  • Một đặc điểm cung cấp một tập hợp các phương pháp thực hiện hành vi.
  • Một đặc điểm yêu cầu một tập hợp các phương thức đóng vai trò là tham số cho hành vi được cung cấp.
  • [...]

Schärli và cộng sự, Đặc điểm: Đơn vị hành vi có thể sử dụng một lần, ECOOP'2003, LNCS 2743, trang 248–274, Springer Verlag, 2003, Trang 2

Vì vậy, sẽ thích hợp hơn nếu nói rằng bạn muốn một đặc điểm yêu cầu một giao diện chứ không phải "triển khai" nó.

Tôi không thấy lý do tại sao không thể có tính năng "đặc điểm yêu cầu (các lớp người tiêu dùng của nó để triển khai) một giao diện" trong PHP, nhưng hiện tại nó dường như bị thiếu.

Như @Danack lưu ý trong câu trả lời của mình , bạn có thể sử dụng các hàm trừu tượng trong đặc điểm để "yêu cầu" chúng từ các lớp sử dụng đặc điểm. Thật không may, bạn không thể làm điều này với các chức năng riêng tư .


1

Tôi đồng ý với phản hồi của @Danack, tuy nhiên tôi sẽ bổ sung một chút.

Phiên bản thực sự ngắn đơn giản hơn vì bạn không thể. Đó không phải là cách Traits hoạt động.

Tôi chỉ có thể nghĩ đến một số trường hợp trong đó những gì bạn yêu cầu là cần thiết và rõ ràng hơn là một vấn đề thiết kế hơn là một lỗi ngôn ngữ. Chỉ cần tưởng tượng rằng có một giao diện như thế này:

interface Weaponize
{
    public function hasAmmunition();
    public function pullTrigger();
    public function fire();
    public function recharge();
}

Một đặc điểm đã được tạo để triển khai một trong các chức năng được xác định trong giao diện nhưng trong quá trình này, sử dụng các chức năng khác cũng được xác định bởi giao diện , dễ xảy ra lỗi rằng nếu lớp sử dụng tính năng không triển khai giao diện, mọi thứ sẽ không kéo được Kích hoạt

trait Triggerable
{
    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}

class Warrior
{
    use Triggerable;
}

Một giải pháp dễ dàng đơn giản là buộc lớp sử dụng đặc điểm cũng thực hiện các chức năng đó:

trait Triggerable
{
    public abstract function hasAmmunition();
    public abstract function fire();

    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}

Vì vậy, đặc điểm không hoàn toàn phụ thuộc vào giao diện , mà là một đề xuất để triển khai một trong các chức năng của nó, vì khi sử dụng đặc điểm , lớp sẽ yêu cầu thực hiện các phương thức trừu tượng.

Thiết kế cuối cùng

interface Weaponize
{
    public function hasAmmunition();
    public function pullTrigger();
    public function fire();
    public function recharge();
}

trait Triggerable
{
    public abstract function hasAmmunition();
    public abstract function fire();

    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}


class Warrior implements Weaponize
{
    use Triggerable;

    public function hasAmmunition()
    {
        // TODO: Implement hasAmmunition() method.
    }

    public function fire()
    {
        // TODO: Implement fire() method.
    }

    public function recharge()
    {
        // TODO: Implement recharge() method.
    }
}

Xin lỗi tiếng anh của tôi

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.