Tại sao PHP 5.2+ không cho phép các phương thức lớp tĩnh trừu tượng?


121

Sau khi bật cảnh báo nghiêm ngặt trong PHP 5.2, tôi đã thấy một loạt cảnh báo tiêu chuẩn nghiêm ngặt từ một dự án ban đầu được viết mà không có cảnh báo nghiêm ngặt:

Tiêu chuẩn nghiêm ngặt : Hàm tĩnh Chương trình :: getSelectSQL () không được trừu tượng trong Program.class.inc

Hàm được đề cập thuộc về Chương trình lớp cha trừu tượng và được khai báo là static trừu tượng vì nó phải được triển khai trong các lớp con của nó, chẳng hạn như TVProgram.

Tôi đã tìm thấy tham chiếu đến thay đổi này ở đây :

Đã bỏ các hàm lớp tĩnh trừu tượng. Do có sự giám sát, PHP 5.0.x và 5.1.x đã cho phép các hàm tĩnh trừu tượng trong các lớp. Đối với PHP 5.2.x, chỉ có giao diện mới có thể có chúng.

Câu hỏi của tôi là: ai đó có thể giải thích một cách rõ ràng tại sao không nên có một hàm tĩnh trừu tượng trong PHP không?


12
Độc giả mới nên lưu ý rằng hạn chế vô lý này đã được gỡ bỏ trong PHP 7.
Đánh dấu Amery

Câu trả lời:


76

các phương thức static thuộc về lớp đã khai báo chúng. Khi mở rộng lớp, bạn có thể tạo một phương thức tĩnh cùng tên, nhưng trên thực tế bạn không thực hiện một phương thức trừu tượng tĩnh.

Tương tự với việc mở rộng bất kỳ lớp nào bằng các phương thức tĩnh. Nếu bạn mở rộng lớp đó và tạo một phương thức tĩnh có cùng chữ ký, bạn không thực sự ghi đè phương thức tĩnh của lớp cha

EDIT (ngày 16 tháng 9 năm 2009)
Cập nhật về điều này. Chạy PHP 5.3, tôi thấy trừu tượng static đã trở lại, dù tốt hay xấu. (xem http://php.net/lsb để biết thêm thông tin)

CHỈNH SỬA (bằng philfreo)
abstract staticvẫn không được phép trong PHP 5.3, LSB có liên quan nhưng khác.


3
OK, vậy điều gì sẽ xảy ra nếu tôi muốn thực thi nhu cầu về hàm getSelectSQL () trong tất cả các phần tử con mở rộng lớp trừu tượng của tôi? getSelectSQL () trong lớp cha không có lý do hợp lệ để tồn tại. Kế hoạch hành động tốt nhất là gì? Lý do tôi chọn trừu tượng static là mã sẽ không biên dịch cho đến khi tôi triển khai getSelectSQL () trong tất cả các phần tử con.
Artem Russakovskii

1
Rất có thể, bạn nên thiết kế lại mọi thứ để getSelectSQL () là một phương thức trừu tượng / instance /. Bằng cách đó, / instance / của mỗi đứa trẻ sẽ có một phương thức như vậy.
Matthew Flaschen

7
Tĩnh trừu tượng vẫn không được phép trong PHP 5.3. Các ràng buộc tĩnh muộn không liên quan gì đến nó. Xem thêm stackoverflow.com/questions/2859633
Artefacto

40
Theo ý kiến ​​của tôi, cảnh báo nghiêm ngặt này thật ngu ngốc vì PHP có "late static binding", tự nhiên cung cấp ý tưởng sử dụng các phương thức tĩnh như thể bản thân lớp đã là một đối tượng (ví dụ như trong ruby). Điều này dẫn đến quá tải phương thức tĩnh và abstract staticcó thể hữu ích trong trường hợp này.
dmitry

4
Câu trả lời này là sai một cách mơ hồ. "vẫn không được phép" chỉ có nghĩa là bạn sẽ nhận được cảnh báo mức E_STRICT, ít nhất trong 5.3+, bạn hoàn toàn được chào đón để tạo các hàm tĩnh trừu tượng, triển khai chúng trong các lớp mở rộng và sau đó tham chiếu đến chúng qua từ khóa static ::. Rõ ràng là phiên bản tĩnh của lớp cha vẫn ở đó và không thể được gọi trực tiếp (thông qua self :: hoặc static :: bên trong lớp đó) vì nó trừu tượng và sẽ bị lỗi nghiêm trọng như thể bạn gọi một hàm trừu tượng không tĩnh thông thường. Về mặt chức năng, điều này rất hữu ích, tôi đồng ý với quan điểm của @dmitry về hiệu quả đó.
ahoffner

79

Đó là một câu chuyện dài và buồn.

Khi PHP 5.2 lần đầu tiên đưa ra cảnh báo này, các liên kết tĩnh muộn vẫn chưa có trong ngôn ngữ này. Trong trường hợp bạn không quen với các ràng buộc tĩnh muộn, hãy lưu ý rằng mã như thế này không hoạt động theo cách bạn có thể mong đợi:

<?php

abstract class ParentClass {
    static function foo() {
        echo "I'm gonna do bar()";
        self::bar();
    }

    abstract static function bar();
}

class ChildClass extends ParentClass {
    static function bar() {
        echo "Hello, World!";
    }
}

ChildClass::foo();

Bỏ qua cảnh báo chế độ nghiêm ngặt, mã trên không hoạt động. Lời self::bar()gọi trong foo()đề cập đến bar()phương thức của ParentClass, ngay cả khi foo()được gọi như một phương thức của ChildClass. Nếu bạn cố gắng chạy mã này với chế độ nghiêm ngặt đang tắt, bạn sẽ thấy " Lỗi nghiêm trọng PHP: Không thể gọi phương thức trừu tượng ParentClass :: bar () ".

Do đó, các phương thức tĩnh trừu tượng trong PHP 5.2 là vô dụng. Các toàn bộ điểm của việc sử dụng một phương pháp trừu tượng là bạn có thể viết mã mà các cuộc gọi phương pháp này mà không biết những gì thực hiện nó sẽ được gọi điện thoại - và sau đó cung cấp triển khai khác nhau trên lớp con khác nhau. Nhưng vì PHP 5.2 không cung cấp cách nào rõ ràng để viết một phương thức của lớp cha gọi một phương thức tĩnh của lớp con mà nó được gọi, nên việc sử dụng các phương thức tĩnh trừu tượng này là không thể. Do đó, bất kỳ cách sử dụng nào abstract statictrong PHP 5.2 đều là mã xấu, có thể được lấy cảm hứng từ sự hiểu nhầm về cách selfhoạt động của từ khóa. Hoàn toàn hợp lý khi đưa ra một cảnh báo về điều này.

Nhưng sau đó PHP 5.3 được bổ sung thêm khả năng tham chiếu đến lớp mà một phương thức được gọi thông qua statictừ khóa (không giống như selftừ khóa, luôn đề cập đến lớp mà phương thức được định nghĩa ). Nếu bạn thay đổi self::bar()thành static::bar()trong ví dụ của tôi ở trên, nó hoạt động tốt trong PHP 5.3 trở lên. Bạn có thể đọc thêm về selfvs statictại New self vs. new static .

Với từ khóa static được thêm vào, lập luận rõ ràng về việc abstract staticđưa ra cảnh báo đã không còn nữa. Mục đích chính của late static bindings là cho phép các phương thức được định nghĩa trong lớp cha gọi các phương thức tĩnh sẽ được định nghĩa trong các lớp con; cho phép các phương thức tĩnh trừu tượng có vẻ hợp lý và nhất quán với sự tồn tại của các ràng buộc tĩnh muộn.

Tôi đoán bạn vẫn có thể tạo ra một trường hợp để giữ cảnh báo. Ví dụ, bạn có thể tranh luận rằng kể từ PHP cho phép bạn gọi các phương thức tĩnh của lớp trừu tượng, trong ví dụ của tôi ở trên (ngay cả sau khi sửa chữa nó bằng cách thay thế selfvới static) bạn đang phơi bày một phương pháp nào ParentClass::foo()đó được phá vỡ và rằng bạn không thực sự muốn lộ ra. Sử dụng một lớp non-static - nghĩa là, tạo tất cả các phương thức instance của phương thức và làm cho phần con của ParentClasstất cả chúng trở thành singleleton hoặc một cái gì đó - sẽ giải quyết vấn đề này, vì ParentClassnó là trừu tượng, không thể được khởi tạo và vì vậy các phương thức instance của nó không thể được gọi là. Tôi nghĩ lập luận này là yếu (bởi vì tôi nghĩ rằngParentClass::foo() không phải là một vấn đề lớn và việc sử dụng các lớp đơn thay vì các lớp tĩnh thường không cần thiết phải dài dòng và xấu xí), nhưng bạn có thể không đồng ý một cách hợp lý - đó là một cách gọi hơi chủ quan.

Vì vậy, dựa trên lập luận này, các nhà phát triển PHP đã giữ cảnh báo trong ngôn ngữ, phải không?

Uh, không chính xác .

Báo cáo lỗi PHP 53081, được liên kết ở trên, đã yêu cầu loại bỏ cảnh báo vì việc bổ sung static::foo()cấu trúc đã làm cho các phương thức tĩnh trừu tượng trở nên hợp lý và hữu ích. Rasmus Lerdorf (người tạo ra PHP) bắt đầu bằng cách gắn nhãn yêu cầu là không có thật và trải qua một chuỗi dài các lý do tồi tệ để cố gắng biện minh cho cảnh báo. Sau đó, cuối cùng, cuộc trao đổi này diễn ra:

Giorgio

tôi biết nhưng:

abstract class cA
{
      //static function A(){self::B();} error, undefined method
      static function A(){static::B();} // good
      abstract static function B();
}

class cB extends cA
{
    static function B(){echo "ok";}
}

cB::A();

Rasmus

Đúng, đó chính xác là cách nó sẽ hoạt động.

Giorgio

nhưng nó không được phép :(

Rasmus

Những gì không được phép?

abstract class cA {
      static function A(){static::B();}
      abstract static function B();
}

class cB extends cA {
    static function B(){echo "ok";}
}

cB::A();

Điều này hoạt động tốt. Rõ ràng là bạn không thể gọi self :: B (), nhưng static :: B () thì ổn.

Tuyên bố của Rasmus rằng mã trong ví dụ của anh ta "hoạt động tốt" là sai; như bạn biết, nó đưa ra một cảnh báo chế độ nghiêm ngặt. Tôi đoán anh ấy đang kiểm tra mà không bật chế độ nghiêm ngặt. Bất chấp điều đó, một Rasmus bối rối đã khiến yêu cầu bị đóng lại một cách sai lầm là "không có thật".

Và đó là lý do tại sao cảnh báo vẫn còn bằng ngôn ngữ. Đây có thể không phải là một lời giải thích hoàn toàn thỏa mãn - bạn có thể đến đây với hy vọng có một lời biện minh hợp lý cho lời cảnh báo. Thật không may, trong thế giới thực, đôi khi những lựa chọn được sinh ra từ những sai lầm trần tục và những suy luận tồi tệ hơn là từ những quyết định hợp lý. Đây chỉ đơn giản là một trong những thời điểm đó.

May mắn thay, Nikita Popov có thể ước tính đã xóa cảnh báo khỏi ngôn ngữ trong PHP 7 như một phần của PHP RFC: Phân loại lại các thông báo E_STRICT . Cuối cùng, sự tỉnh táo đã chiếm ưu thế và một khi PHP 7 được phát hành, tất cả chúng ta có thể vui vẻ sử dụng abstract staticmà không nhận được cảnh báo ngớ ngẩn này.


70

Có một công việc rất đơn giản cho vấn đề này, điều này thực sự có ý nghĩa từ quan điểm thiết kế. Như Jonathan đã viết:

Tương tự với việc mở rộng bất kỳ lớp nào bằng các phương thức tĩnh. Nếu bạn mở rộng lớp đó và tạo một phương thức tĩnh có cùng chữ ký, bạn không thực sự ghi đè phương thức tĩnh của lớp cha

Vì vậy, như một công việc xung quanh bạn có thể làm điều này:

<?php
abstract class MyFoo implements iMyFoo {

    public static final function factory($type, $someData) {
        // don't forget checking and do whatever else you would
        // like to do inside a factory method
        $class = get_called_class()."_".$type;
        $inst = $class::getInstance($someData);
        return $inst;
    }
}


interface iMyFoo {
    static function factory($type, $someData);
    static function getInstance();
    function getSomeData();
}
?>

Và bây giờ bạn thực thi rằng bất kỳ lớp con nào của lớp MyFoo đều triển khai phương thức tĩnh getInstance và phương thức getSomeData công khai. Và nếu bạn không phân lớp MyFoo, bạn vẫn có thể triển khai iMyFoo để tạo một lớp có chức năng tương tự.


2
Có thể với mẫu này để làm cho chức năng được bảo vệ . Khi tôi làm điều đó, điều đó có nghĩa là các lớp mở rộng cảnh báo ném MyFoo rằng getInstance phải được công khai. Và bạn không thể đặt bảo vệ trong một định nghĩa giao diện.
artfulrobot

3
một số thời điểm static::có thể hữu ích.
seyed

3
Không hoạt động với Đặc điểm. Nếu chỉ có Traits có thể có abstract staticcác phương thức, không có PHP bitching ....
Rudie

1
Việc sử dụng một giao diện có lẽ là giải pháp tốt nhất ở đây. +1.
Juan Carlos Coto

Điều này thực sự rất thanh lịch vì sự đơn giản tuyệt đối của nó. +1
G. Stewart

12

Tôi biết điều này đã cũ nhưng….

Tại sao không chỉ ném một ngoại lệ cho phương thức tĩnh của lớp cha đó, theo cách đó nếu bạn không ghi đè nó thì ngoại lệ sẽ được gây ra.


1
Điều đó không giúp ích được gì, ngoại lệ sẽ xảy ra khi gọi phương thức tĩnh - đồng thời lỗi 'phương thức không tồn tại' sẽ xuất hiện nếu bạn không ghi đè nó.
BT

3
@BT Ý tôi là, không khai báo phương thức trừu tượng, thực hiện nó, nhưng chỉ ném một ngoại lệ khi nó được gọi, có nghĩa là nó sẽ không ném nếu nó bị ghi đè.
Petah

Đây có vẻ là giải pháp thanh lịch nhất.
Alex S

Tốt hơn nên xem một cái gì đó như thế này tại thời gian biên dịch, hơn là thời gian chạy. Dễ dàng hơn để tìm thấy vấn đề trước khi chúng được sản xuất bởi vì bạn chỉ cần tải các tập tin, không thực thi mã với con số ra nếu xấu hoặc không phù hợp
Rahly

4

Tôi lập luận rằng một lớp / giao diện trừu tượng có thể được coi như một hợp đồng giữa các lập trình viên. Nó đề cập nhiều hơn đến việc mọi thứ sẽ trông như thế nào / hoạt động như thế nào và không triển khai chức năng thực tế. Như đã thấy trong php5.0 và 5.1.x, đó không phải là quy luật tự nhiên ngăn cản các nhà phát triển php làm điều đó, mà là sự thôi thúc đi cùng với các mẫu thiết kế OO bằng các ngôn ngữ khác. Về cơ bản, những ý tưởng này cố gắng ngăn chặn hành vi không mong muốn, nếu một người đã quen thuộc với các ngôn ngữ khác.


Mặc dù không phải PHP liên quan ở đây là một lời giải thích tốt: stackoverflow.com/questions/3284/...
merkuro

3
Chúa tôi! Hai người đã từ chối bạn hoàn toàn không có tâm trí của họ! Đây là câu trả lời sâu sắc nhất về chủ đề này.
Theodore R. Smith vào

5
@ TheodoreR.Smith Insightful? Nó có lỗi và hầu như không mạch lạc. Tuyên bố rằng các lớp trừu tượng "không triển khai chức năng thực tế" không nhất thiết đúng và đó là toàn bộ điểm của chúng tồn tại ngoài các giao diện. Trong tuyên bố rằng "đó không phải là quy luật tự nhiên ngăn cản các nhà phát triển php làm điều đó" , tôi không biết "nó" là gì. Và trong tuyên bố rằng "Về cơ bản những ý tưởng này cố gắng ngăn chặn hành vi không mong muốn" , tôi không biết "những ý tưởng này" hay "hành vi không mong đợi" tiềm năng là gì. Bất cứ cái nhìn sâu sắc nào bạn rút ra từ điều này, nó bị mất vào tôi.
Mark Amery

2

Tôi không thấy có lý do gì để cấm các hàm trừu tượng tĩnh. Lập luận tốt nhất rằng không có lý do gì để cấm chúng là chúng được phép sử dụng trong Java. Các câu hỏi là: - Về mặt kỹ thuật có khả thi không? - Có, vì đã tồn tại trong PHP 5.2 và chúng tồn tại trong Java. Vì vậy, khi nào CÓ THỂ làm được. CÓ NÊN làm nó không? - Chúng có ý nghĩa không? Đúng. Việc triển khai một phần của một lớp và để lại một phần khác của lớp cho người dùng là hợp lý. Nó có ý nghĩa với các hàm không tĩnh, tại sao nó không có ý nghĩa đối với các hàm tĩnh? Một cách sử dụng các hàm tĩnh là các lớp không được có nhiều hơn một thể hiện (đơn lẻ). Ví dụ một công cụ mã hóa. Nó không cần phải tồn tại trong một số trường hợp và có những lý do để ngăn chặn điều này - ví dụ, bạn chỉ phải bảo vệ một phần của bộ nhớ khỏi những kẻ xâm nhập. Vì vậy, hoàn toàn hợp lý khi triển khai một phần của engine và để lại thuật toán mã hóa cho người dùng. Đây chỉ là một ví dụ. Nếu bạn đã quen với việc sử dụng các hàm tĩnh, bạn sẽ tìm thấy nhiều thứ khác.


3
Quan điểm của bạn về các phương thức tĩnh trừu tượng hiện có trong 5.2 là sai lầm rất nhiều. Thứ nhất, cảnh báo chế độ nghiêm ngặt cấm họ đã được giới thiệu trong 5.2; bạn làm cho nó giống như trong 5.2 họ đã được phép. Thứ hai, trong 5.2, chúng không thể dễ dàng được sử dụng "để triển khai một phần của một lớp và để lại một phần khác của lớp cho người dùng" vì Liên kết tĩnh muộn chưa tồn tại.
Mark Amery,

0

Trong php 5.4+ sử dụng đặc điểm:

trait StaticExample {
    public static function instance () {
    return new self;
    }
}

và trong lớp của bạn đặt tại ăn mày:

use StaticExample;

1
Tôi đã có thể đặt abstract public static function get_table_name();một đặc điểm và sử dụng đặc điểm đó trong lớp trừu tượng của mình mà không cần cảnh báo E_STRICT nữa! Điều này vẫn thực thi việc xác định phương thức tĩnh trong trẻ em như tôi đã hy vọng. Tuyệt diệu!
trình

-1

Xem xét các vấn đề 'Liên kết tĩnh muộn' của PHP. Nếu bạn đang đặt các phương thức tĩnh trên các lớp trừu tượng, có thể bạn sẽ sớm gặp phải nó. Có nghĩa là các cảnh báo nghiêm ngặt đang nói với bạn để tránh sử dụng các tính năng ngôn ngữ bị hỏng.


4
Tôi nghĩ ý anh ấy là những lời cảnh báo về "Tiêu chuẩn nghiêm ngặt".
Jacob Hume

2
Bạn cho rằng các ràng buộc tĩnh trễ có "vấn đề" gì? Bạn khẳng định rằng chúng "bị hỏng", đó là một tuyên bố táo bạo, được đưa ra ở đây mà không có bằng chứng hoặc giải thích. Tính năng này luôn hoạt động tốt đối với tôi, và tôi nghĩ bài đăng này vô nghĩa.
Mark Amery
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.