Đặc điểm so với giao diện


344

Gần đây tôi đã cố gắng nghiên cứu về PHP và tôi thấy mình bị treo lên về những đặc điểm. Tôi hiểu khái niệm tái sử dụng mã ngang và không muốn nhất thiết phải kế thừa từ một lớp trừu tượng. Điều tôi không hiểu là: sự khác biệt quan trọng giữa việc sử dụng các đặc điểm so với giao diện là gì?

Tôi đã cố gắng tìm kiếm một bài viết hay bài viết trên blog để giải thích khi nào nên sử dụng cái này hay cái khác, nhưng các ví dụ mà tôi thấy cho đến nay dường như rất giống nhau.


6
giao diện không có bất kỳ mã nào trong các cơ quan chức năng. họ thực sự không có bất kỳ cơ quan chức năng.
hakre

2
Mặc dù câu trả lời được đánh giá cao của tôi, tôi muốn nó được ghi trong hồ sơ rằng tôi nói chung là chống tính trạng / mixin . Kiểm tra bảng điểm trò chuyện này để đọc cách các đặc điểm thường làm suy yếu các thực hành OOP rắn .
rdlowrey

2
Tôi sẽ tranh luận ngược lại. Đã làm việc với PHP trong nhiều năm trước và kể từ khi xuất hiện các đặc điểm, tôi nghĩ thật dễ dàng để chứng minh giá trị của chúng. Chỉ cần đọc qua ví dụ thực tế này cho phép 'các mô hình hình ảnh' cũng có thể đi lại và nói chuyện như Imagickcác đối tượng, ít hơn tất cả sự phình to cần thiết trong những ngày xưa trước các đặc điểm.
quickshiftin

Đặc điểm và giao diện là tương tự nhau. Sự khác biệt chính là các Đặc điểm cho phép bạn thực hiện các phương thức, Giao diện không.
Giăng

Câu trả lời:


238

Một giao diện định nghĩa một tập các phương thức mà lớp triển khai phải thực hiện.

Khi một đặc điểm là use'việc triển khai các phương thức cũng xuất hiện - điều đó không xảy ra trong một Interface.

Đó là sự khác biệt lớn nhất.

Từ tái sử dụng ngang cho RFC PHP :

Traits là một cơ chế để tái sử dụng mã trong các ngôn ngữ kế thừa đơn lẻ như PHP. Một Trait nhằm giảm một số hạn chế của kế thừa đơn bằng cách cho phép nhà phát triển sử dụng lại các bộ phương thức một cách tự do trong một số lớp độc lập sống trong các hệ thống phân cấp lớp khác nhau.


2
@JREAM Trong thực tế, không có gì. Trong thực tế, nhiều hơn nữa.
Hẻm núi Alec

79
Ngoại trừ những đặc điểm đó không phải là giao diện. Các giao diện là đặc điểm kỹ thuật có thể được kiểm tra đối với. Các đặc điểm không thể được kiểm tra đối với, do đó chúng chỉ được thực hiện. Chúng là đối diện chính xác của giao diện. Dòng đó trong RFC đơn giản là sai ...
ircmaxell

195
Đặc điểm chủ yếu là ngôn ngữ hỗ trợ sao chép và dán .
Shahid

10
Đó không phải là một phép ẩn dụ. Đó là đánh cắp ý nghĩa của một từ. Nó giống như mô tả một hộp như một bề mặt với khối lượng.
cleong

6
Để mở rộng nhận xét của ircmaxell và Shadi: Bạn có thể kiểm tra xem một đối tượng có thực hiện giao diện (thông qua thể hiện) hay không và bạn có thể đảm bảo rằng một đối số phương thức thực hiện giao diện thông qua một gợi ý kiểu trong chữ ký phương thức. Bạn không thể thực hiện kiểm tra tương ứng cho các đặc điểm.
Brian D'Astous

530

Thông báo dịch vụ công cộng:

Tôi muốn tuyên bố về hồ sơ mà tôi tin rằng các đặc điểm hầu như luôn luôn là mùi mã và nên tránh để ủng hộ thành phần. Theo ý kiến ​​của tôi, việc thừa kế đơn lẻ thường bị lạm dụng đến mức trở thành một kiểu chống và nhiều kế thừa chỉ làm cho vấn đề này trở nên phức tạp. Bạn sẽ được phục vụ tốt hơn nhiều trong hầu hết các trường hợp bằng cách ưu tiên thành phần hơn thừa kế (có thể là đơn hoặc nhiều). Nếu bạn vẫn quan tâm đến các đặc điểm và mối quan hệ của chúng với các giao diện, hãy đọc tiếp ...


Hãy bắt đầu bằng cách nói điều này:

Lập trình hướng đối tượng (OOP) có thể là một mô hình khó nắm bắt. Chỉ vì bạn đang sử dụng các lớp không có nghĩa là mã của bạn là Hướng đối tượng (OO).

Để viết mã OO, bạn cần hiểu rằng OOP thực sự là về khả năng của các đối tượng của bạn. Bạn đã phải suy nghĩ về các lớp học về những gì họ có thể làm thay vì những gì họ thực sự làm . Điều này trái ngược hoàn toàn với lập trình thủ tục truyền thống, nơi tập trung vào việc tạo ra một chút mã "làm một cái gì đó".

Nếu mã OOP là về lập kế hoạch và thiết kế, một giao diện là bản thiết kế và một đối tượng là ngôi nhà được xây dựng đầy đủ. Trong khi đó, các đặc điểm chỉ đơn giản là một cách để giúp xây dựng ngôi nhà được đặt ra bởi bản thiết kế (giao diện).

Giao diện

Vậy, tại sao chúng ta nên sử dụng giao diện? Rất đơn giản, các giao diện làm cho mã của chúng tôi ít giòn hơn. Nếu bạn nghi ngờ tuyên bố này, hãy hỏi bất cứ ai bị buộc phải duy trì mã kế thừa không được viết trên giao diện.

Giao diện là một hợp đồng giữa lập trình viên và mã của anh ấy / cô ấy. Giao diện nói: "Miễn là bạn chơi theo luật của tôi, bạn có thể triển khai tôi theo cách bạn thích và tôi hứa tôi sẽ không phá mã khác của bạn."

Vì vậy, ví dụ, hãy xem xét một kịch bản trong thế giới thực (không có ô tô hoặc vật dụng):

Bạn muốn triển khai hệ thống lưu trữ cho ứng dụng web để giảm tải máy chủ

Bạn bắt đầu bằng cách viết một lớp để lưu các yêu cầu phản hồi bằng APC:

class ApcCacher
{
  public function fetch($key) {
    return apc_fetch($key);
  }
  public function store($key, $data) {
    return apc_store($key, $data);
  }
  public function delete($key) {
    return apc_delete($key);
  }
}

Sau đó, trong đối tượng phản hồi HTTP, bạn kiểm tra lần truy cập bộ đệm trước khi thực hiện tất cả công việc để tạo phản hồi thực tế:

class Controller
{
  protected $req;
  protected $resp;
  protected $cacher;

  public function __construct(Request $req, Response $resp, ApcCacher $cacher=NULL) {
    $this->req    = $req;
    $this->resp   = $resp;
    $this->cacher = $cacher;

    $this->buildResponse();
  }

  public function buildResponse() {
    if (NULL !== $this->cacher && $response = $this->cacher->fetch($this->req->uri()) {
      $this->resp = $response;
    } else {
      // Build the response manually
    }
  }

  public function getResponse() {
    return $this->resp;
  }
}

Cách tiếp cận này hoạt động tuyệt vời. Nhưng có thể vài tuần sau bạn quyết định bạn muốn sử dụng hệ thống bộ đệm dựa trên tệp thay vì APC. Bây giờ bạn phải thay đổi mã trình điều khiển của mình vì bạn đã lập trình bộ điều khiển của mình hoạt động với chức năng của ApcCacherlớp thay vì giao diện thể hiện các khả năng của ApcCacherlớp. Hãy nói thay vì ở trên, bạn đã làm cho Controllerlớp học dựa vào CacherInterfacethay vì cụ thể ApcCachernhư vậy:

// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)

Để đi cùng với đó, bạn xác định giao diện của mình như vậy:

interface CacherInterface
{
  public function fetch($key);
  public function store($key, $data);
  public function delete($key);
}

Đổi lại, bạn có cả lớp và lớp ApcCachermới của bạn FileCachertriển khai CacherInterfacevà bạn lập trình Controllerlớp của mình để sử dụng các khả năng theo giao diện.

Ví dụ này (hy vọng) cho thấy cách lập trình với giao diện cho phép bạn thay đổi việc triển khai bên trong các lớp mà không phải lo lắng nếu các thay đổi sẽ phá vỡ mã khác của bạn.

Đặc điểm

Các đặc điểm, mặt khác, chỉ đơn giản là một phương pháp để sử dụng lại mã. Các giao diện không nên được coi là một thay thế loại trừ lẫn nhau cho các đặc điểm. Trong thực tế, việc tạo ra các đặc điểm đáp ứng các khả năng mà giao diện yêu cầu là trường hợp sử dụng lý tưởng .

Bạn chỉ nên sử dụng các đặc điểm khi nhiều lớp chia sẻ cùng một chức năng (có thể được quyết định bởi cùng một giao diện). Không có ý nghĩa trong việc sử dụng một đặc điểm để cung cấp chức năng cho một lớp duy nhất: điều đó chỉ làm xáo trộn những gì lớp đó làm và một thiết kế tốt hơn sẽ chuyển chức năng của tính trạng đó vào lớp có liên quan.

Xem xét việc thực hiện các đặc điểm sau:

interface Person
{
    public function greet();
    public function eat($food);
}

trait EatingTrait
{
    public function eat($food)
    {
        $this->putInMouth($food);
    }

    private function putInMouth($food)
    {
        // Digest delicious food
    }
}

class NicePerson implements Person
{
    use EatingTrait;

    public function greet()
    {
        echo 'Good day, good sir!';
    }
}

class MeanPerson implements Person
{
    use EatingTrait;

    public function greet()
    {
        echo 'Your mother was a hamster!';
    }
}

Một ví dụ cụ thể hơn: hãy tưởng tượng cả bạn FileCachervà bạnApcCacher từ cuộc thảo luận về giao diện đều sử dụng cùng một phương pháp để xác định xem một mục lưu trữ có bị cũ hay không và nên xóa (rõ ràng đây không phải là trường hợp thực tế, nhưng đi cùng với nó). Bạn có thể viết một đặc điểm và cho phép cả hai lớp sử dụng nó cho yêu cầu giao diện chung.

Một lời cảnh báo cuối cùng: hãy cẩn thận đừng quá nhiệt tình với những đặc điểm. Thông thường các đặc điểm được sử dụng như một cái nạng cho thiết kế kém khi việc triển khai lớp duy nhất sẽ đủ. Bạn nên hạn chế các đặc điểm để đáp ứng các yêu cầu giao diện để thiết kế mã tốt nhất.


69
Tôi đã thực sự tìm kiếm câu trả lời đơn giản nhanh chóng được cung cấp ở trên, nhưng tôi phải nói rằng bạn đã đưa ra một câu trả lời sâu sắc tuyệt vời sẽ giúp làm cho sự khác biệt rõ ràng hơn cho người khác, kudos.
datguywhowanders

35
"[C] các đặc điểm gian lận đáp ứng các khả năng mà giao diện yêu cầu trong một lớp nhất định là trường hợp sử dụng lý tưởng". Chính xác: +1
Hẻm núi Alec

5
Sẽ công bằng khi nói rằng các đặc điểm trong PHP tương tự như mixin trong các ngôn ngữ khác?
Eno

5
@igorpan Đối với tất cả các tính năng tôi sẽ nói thực hiện đặc điểm của PHP giống như đa kế thừa. Cần lưu ý rằng nếu một đặc điểm trong PHP chỉ định các thuộc tính tĩnh thì mỗi lớp sử dụng đặc điểm đó sẽ có bản sao riêng của thuộc tính tĩnh. Quan trọng hơn ... xem cách bài đăng này hiện đang cực kỳ cao trên SERPs khi truy vấn các đặc điểm tôi sẽ thêm một thông báo dịch vụ công cộng lên đầu trang. Bạn nên đọc nó.
rdlowrey

3
+1 để giải thích sâu. Tôi đến từ một nền ruby, nơi mixins được sử dụng RẤT NHIỀU; chỉ cần thêm hai xu của tôi, một quy tắc tốt mà chúng tôi sử dụng có thể được dịch bằng php là "không thực hiện các phương thức làm thay đổi $ này trong các đặc điểm". Điều này ngăn chặn cả đống phiên gỡ lỗi điên rồ ... Một mixin cũng KHÔNG nên đưa ra bất kỳ giả định nào về lớp mà nó sẽ được trộn vào (hoặc bạn nên làm cho nó rõ ràng và giảm sự phụ thuộc xuống mức tối thiểu). Về vấn đề này, tôi thấy ý tưởng của bạn về đặc điểm thực hiện giao diện tốt đẹp.
m_x

67

A traitvề cơ bản là triển khai PHP của a mixinvà thực sự là một tập hợp các phương thức mở rộng có thể được thêm vào bất kỳ lớp nào thông qua việc bổ sung trait. Các phương thức sau đó trở thành một phần của việc triển khai lớp đó, nhưng không sử dụng tính kế thừa .

Từ Hướng dẫn PHP (nhấn mạnh của tôi):

Các đặc điểm là một cơ chế để tái sử dụng mã trong các ngôn ngữ kế thừa đơn lẻ như PHP. ... Nó là một bổ sung cho thừa kế truyền thống và cho phép cấu thành hành vi theo chiều ngang; đó là việc áp dụng các thành viên của lớp mà không yêu cầu kế thừa.

Một ví dụ:

trait myTrait {
    function foo() { return "Foo!"; }
    function bar() { return "Bar!"; }
}

Với đặc điểm trên được xác định, bây giờ tôi có thể làm như sau:

class MyClass extends SomeBaseClass {
    use myTrait; // Inclusion of the trait myTrait
}

Tại thời điểm này, khi tôi tạo một thể hiện của lớp MyClass, nó có hai phương thức, được gọi foo()bar()- xuất phát từ đó myTrait. Và - lưu ý rằng các traitphương thức được xác định đã có phần thân phương thức - Interfacephương thức được xác định không thể.

Ngoài ra - PHP, giống như nhiều ngôn ngữ khác, sử dụng một mô hình kế thừa duy nhất - có nghĩa là một lớp có thể xuất phát từ nhiều giao diện, nhưng không phải nhiều lớp. Tuy nhiên, một lớp PHP có thể có nhiều traitthể vùi - cho phép lập trình viên bao gồm các phần có thể tái sử dụng - như có thể nếu bao gồm nhiều lớp cơ sở.

Một số điều cần lưu ý:

                      -----------------------------------------------
                      |   Interface   |  Base Class   |    Trait    |
                      ===============================================
> 1 per class         |      Yes      |       No      |     Yes     |
---------------------------------------------------------------------
Define Method Body    |      No       |       Yes     |     Yes     |
---------------------------------------------------------------------
Polymorphism          |      Yes      |       Yes     |     No      |
---------------------------------------------------------------------

Đa hình:

Trong ví dụ trước, nơi MyClass mở rộng SomeBaseClass , MyClass một ví dụ của SomeBaseClass. Nói cách khác, một mảng như SomeBaseClass[] basescó thể chứa các thể hiện của MyClass. Tương tự, nếu được MyClassmở rộng IBaseInterface, một mảng IBaseInterface[] basescó thể chứa các thể hiện của MyClass. Không có cấu trúc đa hình như vậy có sẵn với một trait- vì traitvề cơ bản chỉ là một mã được sao chép để thuận tiện cho người lập trình vào mỗi lớp sử dụng nó.

Quyền ưu tiên:

Như được mô tả trong Hướng dẫn:

Một thành viên được kế thừa từ một lớp cơ sở bị ghi đè bởi một thành viên được chèn bởi Trait. Thứ tự ưu tiên là các thành viên từ lớp hiện tại ghi đè các phương thức Trait, trong đó trả lại ghi đè các phương thức được kế thừa.

Vì vậy - hãy xem xét kịch bản sau đây:

class BaseClass {
    function SomeMethod() { /* Do stuff here */ }
}

interface IBase {
    function SomeMethod();
}

trait myTrait {
    function SomeMethod() { /* Do different stuff here */ }
}

class MyClass extends BaseClass implements IBase {
    use myTrait;

    function SomeMethod() { /* Do a third thing */ }
}

Khi tạo một thể hiện của MyClass, ở trên, xảy ra như sau:

  1. Yêu Interface IBasecầu một hàm không tham số được gọi là SomeMethod()được cung cấp.
  2. Lớp cơ sở BaseClasscung cấp một triển khai của phương thức này - đáp ứng nhu cầu.
  3. Các trait myTraitcung cấp một chức năng parameterless gọi SomeMethod()là tốt, mà được ưu tiên so với BaseClass-version
  4. Việc class MyClasscung cấp phiên bản riêng của nó SomeMethod()- được ưu tiên hơn so với đảo ngược trait.

Phần kết luận

  1. An Interfacekhông thể cung cấp một triển khai mặc định của thân phương thức, trong khi traitcó thể.
  2. An Interfacelà một cấu trúc đa hình , được kế thừa - trong khi a traitthì không.
  3. Nhiều Interfaces có thể được sử dụng trong cùng một lớp và nhiều traits cũng vậy.

4
"Một đặc điểm tương tự như khái niệm C # của một lớp trừu tượng" Không, một lớp trừu tượng là một lớp trừu tượng; khái niệm đó tồn tại trong cả PHP và C #. Thay vào đó, tôi sẽ so sánh một đặc điểm trong PHP với một lớp tĩnh được tạo bằng các phương thức mở rộng trong C #, với hạn chế dựa trên loại được loại bỏ vì một đặc điểm có thể được sử dụng bởi hầu hết mọi loại, không giống như một phương thức mở rộng chỉ mở rộng một loại.
BoltClock

1
Bình luận rất tốt - và tôi đồng ý với bạn. Trong đọc lại, đó là một tương tự tốt hơn. Tuy nhiên, tôi tin rằng vẫn tốt hơn khi nghĩ về nó như một mixin- và khi tôi xem lại phần mở đầu câu trả lời của mình, tôi đã cập nhật để phản ánh điều này. Cảm ơn đã bình luận, @BoltClock!
Troy Alford

1
Tôi không nghĩ rằng có bất kỳ liên quan đến các phương pháp mở rộng c #. Các phương thức mở rộng được thêm vào loại lớp duy nhất (tất nhiên là tôn trọng hệ thống phân cấp lớp) mục đích của chúng là tăng cường một loại với chức năng bổ sung, không "chia sẻ mã" trên nhiều lớp và tạo ra một mớ hỗn độn. Nó không thể so sánh được! Nếu một cái gì đó cần được sử dụng lại, điều đó thường có nghĩa là nó nên có không gian riêng, giống như lớp riêng biệt có liên quan đến các lớp cần chức năng chung. Việc thực hiện có thể khác nhau tùy thuộc vào thiết kế, nhưng đại khái là vậy. Các đặc điểm chỉ là một cách khác để tạo ra một mã kém.
Sofija

Một lớp có thể có nhiều Giao diện? Tôi không chắc chắn nếu tôi nhận được biểu đồ của bạn sai, nhưng lớp X thực hiện Y, Z là hợp lệ.
Yann Chabot

26

Tôi nghĩ traitslà hữu ích để tạo các lớp có chứa các phương thức có thể được sử dụng làm phương thức của một số lớp khác nhau.

Ví dụ:

trait ToolKit
{
    public $errors = array();

    public function error($msg)
    {
        $this->errors[] = $msg;
        return false;
    }
}

Bạn có thể có và sử dụng phương thức "lỗi" này trong bất kỳ lớp nào sử dụng đặc điểm này.

class Something
{
    use Toolkit;

    public function do_something($zipcode)
    {
        if (preg_match('/^[0-9]{5}$/', $zipcode) !== 1)
            return $this->error('Invalid zipcode.');

        // do something here
    }
}

Trong khi với interfacesbạn chỉ có thể khai báo chữ ký phương thức chứ không phải mã của hàm. Ngoài ra, để sử dụng một giao diện, bạn cần tuân theo một hệ thống phân cấp, sử dụng implements. Đây không phải là trường hợp với đặc điểm.

Nó hoàn toàn khác!


Tôi nghĩ rằng đây là một ví dụ xấu về một đặc điểm. to_integersẽ có nhiều khả năng được bao gồm trong một IntegerCastgiao diện vì không có cách nào tương tự về cơ bản với các lớp truyền (thông minh) thành một số nguyên.
Matthew

5
Quên "to_integer" - nó chỉ là một minh họa. Một ví dụ. Một "Xin chào, thế giới". Một "ví dụ.com".
J. Bruni

2
Bộ công cụ này mang lại lợi ích gì cho một lớp tiện ích độc lập không thể? Thay vì use Toolkitbạn có thể có $this->toolkit = new Toolkit();hoặc tôi đang thiếu một số lợi ích của chính đặc điểm này?
Anthony

@Anthony đâu đó trong bạn Something thùng chứa của bạn, bạn làmif(!$something->do_something('foo')) var_dump($something->errors);
TheRealChx101

20

Đối với người mới bắt đầu, câu trả lời trên có thể khó, Đây là cách dễ nhất để hiểu nó:

Đặc điểm

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

Vì vậy, nếu bạn muốn có sayHellochức năng trong các lớp khác mà không cần tạo lại toàn bộ chức năng, bạn có thể sử dụng các đặc điểm,

class MyClass{
  use SayWorld;

}

$o = new MyClass();
$o->sayHello();

Thật tuyệt đúng không!

Không chỉ các chức năng bạn có thể sử dụng bất cứ điều gì trong đặc điểm (chức năng, biến, const ..). bạn cũng có thể sử dụng nhiều đặc điểm:use SayWorld,AnotherTraits;

Giao diện

  interface SayWorld {
     public function sayHello();
  }

  class MyClass implements SayWorld { 
     public function sayHello() {
        echo 'World!';
     }
}

Vì vậy, đây là cách giao diện khác với các đặc điểm: Bạn phải tạo lại mọi thứ trong giao diện trong lớp đã triển khai. Giao diện không có triển khai. và giao diện chỉ có thể có chức năng và const, nó không thể có biến.

Tôi hi vọng cái này giúp được!


5

Một phép ẩn dụ thường được sử dụng để mô tả Đặc điểm là Đặc điểm là các giao diện có triển khai.

Đây là một cách tốt để suy nghĩ về nó trong hầu hết các trường hợp, nhưng có một số khác biệt tinh tế giữa hai.

Để bắt đầu, instanceof toán tử sẽ không hoạt động với các đặc điểm (nghĩa là một đặc điểm không phải là một đối tượng thực sự) vì vậy bạn không thể xem liệu một lớp có một đặc điểm nào đó không (hoặc để xem hai lớp khác có liên quan có chung một đặc điểm không ). Đó là ý nghĩa của nó bởi nó là một cấu trúc để sử dụng lại mã ngang.

Hiện tại có các hàm trong PHP sẽ cho phép bạn có được một danh sách tất cả các đặc điểm mà một lớp sử dụng, nhưng tính kế thừa có nghĩa là bạn sẽ cần phải kiểm tra đệ quy để kiểm tra một cách đáng tin cậy nếu một lớp tại một thời điểm nào đó có một đặc điểm cụ thể không mã trên các trang tài liệu PHP). Nhưng vâng, nó chắc chắn không đơn giản và sạch sẽ như thể hiện, và IMHO là một tính năng giúp PHP trở nên tốt hơn.

Ngoài ra, các lớp trừu tượng vẫn là các lớp, vì vậy chúng không giải quyết các vấn đề sử dụng lại mã liên quan đến nhiều kế thừa. Hãy nhớ rằng bạn chỉ có thể mở rộng một lớp (thực hoặc trừu tượng) nhưng thực hiện nhiều giao diện.

Tôi đã tìm thấy những đặc điểm và giao diện thực sự tốt khi sử dụng tay trong tay để tạo ra nhiều thừa kế giả. Ví dụ:

class SlidingDoor extends Door implements IKeyed  
{  
    use KeyedTrait;  
    [...] // Generally not a lot else goes here since it's all in the trait  
}

Thực hiện điều này có nghĩa là bạn có thể sử dụng thể hiện để xác định xem đối tượng Cửa cụ thể có bị khóa hay không, bạn biết rằng bạn sẽ có một bộ phương thức nhất quán, v.v. và tất cả mã nằm ở một nơi trên tất cả các lớp sử dụng KeyedTrait.


Phần cuối cùng của câu trả lời đó tất nhiên là những gì @rdlowrey đang nói chi tiết hơn trong ba đoạn cuối dưới "Đặc điểm" trong bài đăng của mình; Tôi chỉ cảm thấy một đoạn mã bộ xương thực sự đơn giản sẽ giúp minh họa nó.
Jon Kloske

Tôi nghĩ rằng cách tốt nhất để sử dụng các đặc điểm là sử dụng các giao diện mà bạn có thể. Và nếu có trường hợp khi nhiều lớp con triển khai cùng loại mã cho giao diện đó và bạn không thể di chuyển mã đó sang siêu lớp (trừu tượng) của chúng -> triển khai nó với các đặc điểm
người chơi - một

4

Các đặc điểm chỉ đơn giản là để tái sử dụng mã .

Giao diện chỉ cung cấp chữ ký của các hàm sẽ được xác định trong lớp nơi nó có thể được sử dụng tùy theo ý của lập trình viên . Do đó, cho chúng tôi một nguyên mẫu cho một nhóm các lớp .

Để tham khảo- http://www.php.net/manual/en/lingu.oop5.traits.php


3

Về cơ bản, bạn có thể coi một đặc điểm là "sao chép-dán" mã tự động.

Sử dụng đặc điểm là nguy hiểm vì không có nghĩa là phải biết những gì nó làm trước khi thực hiện.

Tuy nhiên, các đặc điểm linh hoạt hơn vì thiếu các hạn chế như thừa kế.

Các đặc điểm có thể hữu ích để tiêm một phương thức kiểm tra một cái gì đó vào một lớp, ví dụ, sự tồn tại của một phương thức hoặc thuộc tính khác. Một bài viết hay về điều đó (nhưng bằng tiếng Pháp, xin lỗi) .

Đối với những người đọc tiếng Pháp có thể lấy nó, Tạp chí GNU / Linux HS 54 có một bài viết về chủ đề này.


Vẫn không hiểu các đặc điểm khác với các giao diện với cách triển khai mặc định
denis631

@ denis631 Bạn có thể xem Đặc điểm là đoạn mã và giao diện dưới dạng hợp đồng chữ ký. Nếu nó có thể giúp đỡ, bạn có thể xem nó như một bit không chính thức của một lớp có thể chứa bất cứ thứ gì. Hãy cho tôi biết nếu nó giúp.
18:53

Tôi thấy rằng các đặc điểm PHP có thể được xem như là các macro sau đó được mở rộng tại thời gian biên dịch / chỉ cần đặt bí danh cho đoạn mã đó với khóa đó. Tuy nhiên, những đặc điểm của Rust xuất hiện khác nhau (hoặc tôi sai). Nhưng vì cả hai đều có đặc điểm từ trong đó nên tôi cho rằng chúng giống nhau, có nghĩa là cùng một khái niệm. Liên kết các đặc điểm của Rust: doc.rust-lang.org/rust-by-example/trait.html
denis631

2

Nếu bạn biết tiếng Anh và biết traitnghĩa là gì , thì đó chính xác là những gì tên nói. Nó là một gói các phương thức và thuộc tính không có lớp mà bạn đính kèm vào các lớp hiện có bằng cách gõ use.

Về cơ bản, bạn có thể so sánh nó với một biến duy nhất. Các hàm đóng có thể usecác biến này từ bên ngoài phạm vi và theo cách đó chúng có giá trị bên trong. Chúng mạnh mẽ và có thể được sử dụng trong mọi thứ. Điều tương tự xảy ra với các đặc điểm nếu chúng đang được sử dụng.


2

Các câu trả lời khác đã làm rất tốt khi giải thích sự khác biệt giữa các giao diện và đặc điểm. Tôi sẽ tập trung vào một ví dụ thực tế hữu ích, cụ thể là một ví dụ chứng minh rằng các đặc điểm có thể sử dụng các biến thể hiện - cho phép bạn thêm hành vi vào một lớp với mã soạn sẵn tối thiểu.

Một lần nữa, như được đề cập bởi những người khác, các đặc điểm kết hợp tốt với các giao diện, cho phép giao diện chỉ định hợp đồng hành vi và đặc điểm để thực hiện việc thực hiện.

Thêm khả năng xuất bản / đăng ký sự kiện vào một lớp có thể là một tình huống phổ biến trong một số cơ sở mã. Có 3 giải pháp chung:

  1. Xác định một lớp cơ sở với mã quán rượu / mã phụ sự kiện và sau đó các lớp muốn cung cấp các sự kiện có thể mở rộng nó để đạt được các khả năng.
  2. Xác định một lớp với mã quán rượu / mã phụ và sau đó các lớp khác muốn cung cấp các sự kiện có thể sử dụng nó thông qua thành phần, xác định các phương thức riêng của chúng để bọc đối tượng được soạn thảo, ủy quyền các phương thức gọi nó.
  3. Xác định một đặc điểm với mã quán rượu / mã phụ sự kiện và sau đó các lớp khác muốn cung cấp các sự kiện có thể uselà đặc điểm, còn gọi là nhập nó, để đạt được các khả năng.

Làm thế nào tốt mỗi công việc?

# 1 Không hoạt động tốt. Cho đến ngày bạn nhận ra mình không thể mở rộng lớp cơ sở vì bạn đã mở rộng thứ khác. Tôi sẽ không đưa ra một ví dụ về điều này bởi vì rõ ràng việc giới hạn sử dụng quyền thừa kế như thế này là rõ ràng.

# 2 & # 3 đều hoạt động tốt. Tôi sẽ đưa ra một ví dụ làm nổi bật một số khác biệt.

Đầu tiên, một số mã sẽ giống nhau giữa cả hai ví dụ:

Một giao diện

interface Observable {
    function addEventListener($eventName, callable $listener);
    function removeEventListener($eventName, callable $listener);
    function removeAllEventListeners($eventName);
}

Và một số mã để chứng minh việc sử dụng:

$auction = new Auction();

// Add a listener, so we know when we get a bid.
$auction->addEventListener('bid', function($bidderName, $bidAmount){
    echo "Got a bid of $bidAmount from $bidderName\n";
});

// Mock some bids.
foreach (['Moe', 'Curly', 'Larry'] as $name) {
    $auction->addBid($name, rand());
}

Ok, bây giờ hãy cho thấy cách thực hiện Auction lớp sẽ khác nhau như thế nào khi sử dụng các đặc điểm.

Đầu tiên, đây là cách # 2 (sử dụng bố cục) sẽ như thế nào:

class EventEmitter {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    private $eventEmitter;

    public function __construct() {
        $this->eventEmitter = new EventEmitter();
    }

    function addBid($bidderName, $bidAmount) {
        $this->eventEmitter->triggerEvent('bid', [$bidderName, $bidAmount]);
    }

    function addEventListener($eventName, callable $listener) {
        $this->eventEmitter->addEventListener($eventName, $listener);
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventEmitter->removeEventListener($eventName, $listener);
    }

    function removeAllEventListeners($eventName) {
        $this->eventEmitter->removeAllEventListeners($eventName);
    }
}

Đây là cách # 3 (đặc điểm) sẽ như thế nào:

trait EventEmitterTrait {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    protected function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    use EventEmitterTrait;

    function addBid($bidderName, $bidAmount) {
        $this->triggerEvent('bid', [$bidderName, $bidAmount]);
    }
}

Lưu ý rằng mã bên trong EventEmitterTraitgiống hệt như những gì bên trong EventEmitterlớp ngoại trừ đặc điểm khai báo triggerEvent()phương thức là được bảo vệ. Vì vậy, sự khác biệt duy nhất bạn cần xem xét là việc thực hiện Auctionlớp .

Và sự khác biệt là lớn. Khi sử dụng thành phần, chúng tôi nhận được một giải pháp tuyệt vời, cho phép chúng tôi sử dụng lạiEventEmitter bao nhiêu lớp tùy thích. Tuy nhiên, nhược điểm chính là chúng ta có rất nhiều mã soạn sẵn mà chúng ta cần viết và duy trì bởi vì đối với mỗi phương thức được xác định trong Observablegiao diện, chúng ta cần triển khai nó và viết mã soạn sẵn nhàm chán chỉ chuyển tiếp các đối số vào phương thức tương ứng trong chúng tôi sáng tác các EventEmitterđối tượng. Sử dụng đặc điểm trong ví dụ này cho phép chúng tôi tránh điều đó , giúp chúng tôi giảm mã soạn sẵn và cải thiện khả năng bảo trì .

Tuy nhiên, có thể đôi khi bạn không muốn Auctionlớp của mình thực hiện Observablegiao diện đầy đủ - có thể bạn chỉ muốn hiển thị 1 hoặc 2 phương thức hoặc thậm chí không có phương thức nào để bạn có thể xác định chữ ký phương thức của riêng mình. Trong trường hợp như vậy, bạn vẫn có thể thích phương thức sáng tác.

Nhưng, đặc điểm này rất hấp dẫn trong hầu hết các kịch bản, đặc biệt là nếu giao diện có nhiều phương thức, khiến bạn phải viết nhiều bản tóm tắt.

* Bạn thực sự có thể làm cả hai - xác định EventEmitterlớp trong trường hợp bạn muốn sử dụng nó theo cách cấu thành và cũng xác định EventEmitterTraitđặc điểm, sử dụng EventEmitterlớp triển khai bên trong tính trạng :)


1

Đặc điểm này giống như một lớp chúng ta có thể sử dụng cho nhiều mục đích thừa kế và cũng có thể sử dụng lại mã.

Chúng ta có thể sử dụng tính trạng bên trong lớp và chúng ta cũng có thể sử dụng nhiều tính trạng trong cùng một lớp với 'sử dụng từ khóa'.

Giao diện đang sử dụng để tái sử dụng mã giống như một đặc điểm

giao diện được mở rộng nhiều giao diện để chúng ta có thể giải quyết nhiều vấn đề kế thừa nhưng khi chúng ta thực hiện giao diện thì chúng ta nên tạo tất cả các phương thức bên trong lớp. Để biết thêm thông tin nhấp vào liên kết dưới đây:

http://php.net/manual/en/lingu.oop5.traits.php http://php.net/manual/en/lingu.oop5.interfaces.php



0

Sự khác biệt chính là, với các giao diện, bạn phải xác định việc triển khai thực tế của từng phương thức trong mỗi lớp thực hiện giao diện đã nói, để bạn có thể có nhiều lớp thực hiện cùng một giao diện nhưng với hành vi khác nhau, trong khi các đặc điểm chỉ là các đoạn mã được đưa vào một lớp học; Một sự khác biệt quan trọng khác là các phương thức đặc trưng chỉ có thể là phương thức lớp hoặc phương thức tĩnh, không giống như các phương thức giao diện cũng có thể (và thường là) phương thức cá thể.

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.