2009-12-13 17 views
20

tôi sẽ sử dụng các ví dụ sau để minh họa cho câu hỏi của tôi:Có cách nào để xác định lại một gợi ý kiểu cho một lớp con cháu khi mở rộng một lớp trừu tượng không?

class Attribute {} 

class SimpleAttribute extends Attribute {} 



abstract class AbstractFactory { 
    abstract public function update(Attribute $attr, $data); 
} 

class SimpleFactory extends AbstractFactory { 
    public function update(SimpleAttribute $attr, $data); 

} 

Nếu bạn cố gắng chạy này, PHP sẽ ném một lỗi nghiêm trọng, nói rằng Declaration of SimpleFactory::update() must be compatible with that of AbstractFactory::update()

tôi hiểu chính xác những gì này có nghĩa là: Chữ ký phương thức SimpleFactory::update() s phải khớp chính xác với lớp trừu tượng gốc của nó.

Tuy nhiên, câu hỏi của tôi: Có cách nào để cho phép phương pháp cụ thể (trong trường hợp này là SimpleFactory::update()) để xác định lại gợi ý loại cho một hậu duệ hợp lệ của gợi ý ban đầu không?

Một ví dụ sẽ là instanceof điều hành, trong đó sẽ trở thành sự thật trong trường hợp sau đây:

SimpleAttribute instanceof Attribute // => true 

Tôi nhận ra rằng khi một công trình xung quanh, tôi có thể làm cho các loại gợi ý như nhau trong các phương pháp cụ thể, và thực hiện kiểm tra instanceof trong thân phương thức, nhưng có cách nào để thực thi điều này ở mức chữ ký không?

+3

+1 Tôi rất muốn xem liệu điều này có khả thi hay không, mặc dù trong cuộc sống của tôi, tôi không thể hiểu được làm thế nào. Có lẽ PHP6 sẽ có hỗ trợ cải tiến cho phương thức nạp chồng và phát hiện nhiều phương thức * chữ ký *, vì vậy 'cập nhật hàm trừu tượng công cộng (Attribute $ attr, $ data)' và 'abstract abstract function update (SimpleAttribute $ attr, $ data)' có thể đồng tồn tại trong cùng một phạm vi lớp. – leepowers

Trả lời

15

Tôi không mong đợi như vậy, vì nó có thể phá vỡ hợp đồng gợi ý loại. Giả sử một hàm foo lấy một AbstractFactory và được thông qua một SimpleFactory.

function foo(AbstractFactory $maker) { 
    $attr = new Attribute(); 
    $maker->update($attr, 42); 
} 
... 
$packager=new SimpleFactory(); 
foo($packager); 

foo cuộc gọi update và vượt qua một thuộc tính đến nhà máy, mà nó nên đi vì phương pháp chữ ký AbstractFactory::update hứa hẹn có thể mất một thuộc tính. Bam! SimpleFactory có một đối tượng kiểu mà nó không thể xử lý đúng.

class Attribute {} 
class SimpleAttribute extends Attribute { 
    public function spin() {...} 
} 
class SimpleFactory extends AbstractFactory { 
    public function update(SimpleAttribute $attr, $data) { 
     $attr->spin(); // This will fail when called from foo() 
    } 
} 

Trong thuật ngữ hợp đồng, lớp hậu duệ phải tôn trọng hợp đồng của tổ tiên của họ, có nghĩa là các thông số chức năng có thể có được cơ sở hơn/ít hơn quy định/cung cấp một hợp đồng yếu và trở về giá trị có thể được bắt nguồn hơn/nhiều hơn quy định/cung cấp hợp đồng mạnh hơn. Nguyên tắc được mô tả cho Eiffel (được cho là ngôn ngữ thiết kế theo hợp đồng phổ biến nhất) trong "An Eiffel Tutorial: Inheritance and Contracts". Sự suy yếu và tăng cường của các loại là ví dụ của contravariance and covariance, tương ứng.

Trong các thuật ngữ lý thuyết khác, đây là ví dụ về vi phạm LSP. Không, không phải rằngLSP; các Liskov Substitution Principle, trong đó nói rằng các đối tượng của một loại phụ có thể được thay thế cho các đối tượng của một siêu kiểu. SimpleFactory là một loại phụ của AbstractFactoryfoo mất một số AbstractFactory. Do đó, theo LSP, foo phải mất SimpleFactory. Làm như vậy gây ra lỗi "Gọi đến không xác định", có nghĩa là LSP đã bị vi phạm.

+0

Nhưng, trường hợp đầu tiên của bạn sẽ không bao giờ xảy ra vì 'foo' sẽ không chấp nhận một cá thể' SimpleFactory'. Câu hỏi của tôi nghiêng về phía lý do tại sao tôi không thể thực hiện hợp đồng của phương thức 'update' _more strict_ trong nhà máy bê tông. – jason

+1

Trường hợp sử dụng là có nhiều nhà máy cụ thể, nói 'SimpleFactory',' EnumberableFactory' và cứ thế, mỗi cái có nghĩa là chỉ hoạt động theo kiểu thuộc tính riêng của nó, 'SimpleAttribute' và' EnumerableAttribute', tương ứng. Nhưng, vì nó đứng, vì tôi không thể thắt chặt giới hạn gợi ý trong phân lớp nhà máy, tất cả các nhà máy cụ thể sẽ phải chấp nhận tất cả các loại thuộc tính ... chặn một kiểm tra đơn giản ở trên cùng của thân phương thức, đó là tốt, nhưng tôi đã tự hỏi liệu nó có thể ở cấp độ chữ ký hay không. – jason

+0

Tuy nhiên, tôi thực sự đánh giá cao câu trả lời của bạn. – jason

3

Câu trả lời được chấp nhận là chính xác trong việc trả lời OP rằng những gì ông đã cố gắng làm vi phạm Nguyên tắc thay thế Liskov. OP nên sử dụng giao diện mới và sử dụng bố cục thay vì thừa kế để giải quyết vấn đề chữ ký chức năng của mình. Sự thay đổi trong vấn đề ví dụ của OP là khá nhỏ.

class Attribute {} 

class SimpleAttribute extends Attribute {} 

abstract class AbstractFactory { 
    abstract public function update(Attribute $attr, $data); 
} 

interface ISimpleFactory { 
    function update(SimpleAttribute $attr, $data); 
} 

class SimpleFactory implements ISimpleFactory { 
    private $factory; 
    public function __construct(AbstractFactory $factory) 
    { 
     $this->factory = $factory; 
    } 
    public function update(SimpleAttribute $attr, $data) 
    { 
     $this->factory->update($attr, $data); 
    } 

} 

Ví dụ mã trên thực hiện hai điều: 1) Tạo giao diện ISimpleFactory rằng tất cả các mã phụ thuộc vào một nhà máy cho SimpleAttributes sẽ mã chống lại 2) Thực hiện SimpleFactory sử dụng một nhà máy chung sẽ đòi hỏi SimpleFactory đưa qua một constructor Ví dụ của lớp dẫn xuất AbstractFactory mà nó sẽ sử dụng trong hàm cập nhật từ phương thức ghi đè giao diện ISimpleFactory trong SimpleFactory.

Điều này cho phép bất kỳ phụ thuộc nào khi nhà máy chung được đóng gói từ bất kỳ mã nào phụ thuộc vào ISimpleFactory, nhưng cho phép SimpleFactory thay thế bất kỳ nhà máy nào có nguồn gốc từ AbstractFactory (Satisfying LSP) mà không phải thay đổi bất kỳ mã nào (mã gọi cung cấp sự phụ thuộc). Một lớp dẫn xuất mới của ISimpleFactory có thể quyết định không sử dụng bất kỳ cá thể dẫn xuất AbstractFactory nào để thực thi chính nó, và tất cả mã gọi sẽ được che chắn từ chi tiết đó.

Thừa kế có thể có giá trị lớn, tuy nhiên, đôi khi Thành phần bị bỏ qua và cách ưu tiên của tôi là giảm khớp nối chặt chẽ.

+0

Điều gì sẽ được chuyển cho hàm tạo cho 'SimpleFactory' trong quá trình triển khai của bạn? Một thể hiện của một lớp nhà máy khác mà _does_ kế thừa từ 'AbstractFactory'? –

+0

Có nếu ví dụ của tôi được tuân thủ nghiêm ngặt, bạn sẽ chuyển sang một thể hiện của một lớp nhà máy khác mà kế thừa từ AbstractFactory. Trong ví dụ của tôi ở trên đó, SimpleFactory được thực hiện với một AbstractFactory, tuy nhiên, điều này không nhất thiết phải như vậy, ISimpleFactory có thể được triển khai mà không có một AbstractFactory nếu muốn. Ví dụ của tôi chỉ cho thấy rằng bạn có thể ẩn chi tiết triển khai đó, chỉ được tiết lộ bằng mã gọi thông qua quá trình tạo hàm dựng. –

Các vấn đề liên quan