从this站点开始,初始程序实现了“具体化” – 在这种情况下,PDFBook被打字以传递给构造函数.后来,
此类型提示更改为常规EBook界面.接受任何实现此接口的内容.在这种情况下有意义.
但是,即使编码到接口,我发现在接口中通常没有定义额外的方法,但这些方法对于该特定结构是唯一的.
在这种情况下,PDFBook可能有一个方法doDPFOnlyThing未在任何其他实现EBook接口的类中定义.
如果我将一个PDFBook对象传递给myFunc(),它提示类型提示EBook接口,根据我的理解,如果我只使用接口中定义的方法 – read() – 那么这将遵循DIP是吗?因此,传入myFunc()实现接口的任何东西都可以调用它们的read()方法,因为它遵守接口契约.
myFunc(Ebook $book) { $book->read(); }
如果myFunc()必须使用仅在PDFBook类中可用的doDPFOnlyThing(),该怎么办?我假设这会添加依赖关系,因为这种方法只存在于PDFBook具体化中?
myFunc(Ebook $book) { $book->doDPFOnlyThing(); }
在这种情况下,做什么更好?
也就是说,你实际上有两种不同的方法.当调用myFunc并传递EBook时,你绝对应该只依赖于界面中的方法.如果一个方法需要调用doPDFOnlyThing并且它依赖于EBook而不是PDFBook,那么这将违反该原则.
你能做的一件事是:
public myFunc(EBook $book) { $book->read(); } public myPDFFunc(PDFBook $book) { $book->read(); //Still valid from EBook contract $book->doPDFOnlyThing(); }
虽然这可能会起作用,但这是一个肮脏的修复,你可能最终会违反开放/封闭原则,因为你将回来编辑课程. (最终客户需要一台带有doKindleOnlyThing方法的KindleBook.)
那么如何解决这个问题呢?
你想要对一个接口进行类型提示但是使用实现中的方法的问题就像吃蛋糕一样吃了……
要解决这个问题,您需要更多地抽象设计.让我们使用一个示例,您将创建一个客户端,该客户端将读取各种格式的书籍,这些书籍都是从作为基类MyEBook实现的EBook接口派生的.让我们从下面的代码开始:
interface EBook { public function read(); } interface PDFBook extends EBook { public function doPDFOnlyThing(); } class MyEBook implements EBook { public function read() { echo 'reading from a ' . get_class($this); } } class MyPDFBook extends MyEBook implements PDFBook { public function read() { //you only need to override this method //if needed,otherwise you can leave it //out and default to the parent class //implementation. parent::read(); } public function doPDFOnlyThing() { echo 'doing what a PDF does while...'; } }
EBook接口收缩read()方法,PDFBook接口扩展EBook并将doPDFOnlyThing()方法添加到合同中.具体实现MyEBook和MyPDFBook将各自使用它们各自的接口.
接下来,我们需要构建一些处理程序类,它们可以接受任何书籍并对它们执行某种操作.我们将使用一个命名约定,其中所有处理程序类后面都有Reader后缀.所以MyPDFBook的处理程序将是MyPDFBookReader.这个惯例稍后会很方便.
我们将从一个抽象类开始,该类可以接受EBook的任何实现并将其存储在类属性中.该类还希望所有子类都实现一个名为readBook()的方法.
abstract class GenericBookReader { protected $book; public function __construct(EBook $book) { $this->book = $book; } abstract public function readBook(); }
现在我们有了可以接受任何EBook的抽象类,我们可以构建特定的实现,它们将类型提示给特定的接口类 – 例如PDFBook或电子书.
class MyBookReader extends GenericBookReader { public function __construct(EBook $book) { parent::__construct($book); } public function readBook() { $this->book->read(); } } class MyPDFBookReader extends GenericBookReader { public function __construct(PDFBook $book) { parent::__construct($book); } public function readBook() { //You are safe to use PDFBook methods here //because you have a guarantee they are available $this->book->doPDFOnlyThing(); $this->book->read(); } }
这两个具体实现都只是将$book中的给定对象发送到父构造函数,然后父构造函数将其缓存在$this-> book属性中.初始化时需要对任何书籍执行的任何操作都可以在GenericBookReader中完成,所有类都将使用新方法,而不必单独更新.当然,如果一个特定的类需要一些特殊的初始化,可以在它们自己的构造函数而不是父构造函数中完成.
此时,您已经在自己的处理程序中而不是单个类中将EBook和PDFBook相互抽象.这是向前迈出的一步,因为现在在MyPDFBookReader类的readBook()方法中,您可以保证doPDFOnlyThing()可供使用.
现在将所有这些粘合在一起,你需要一个阅读书籍的客户.客户端应该能够接受任何EBook,确定它的类型,创建适当的Reader类,然后调用readBook()方法.命名约定在这里很好用,因为我们可以动态地构建类名.
class BookClient { public function readBook(EBook $book) { //Get the class name of $book $name = get_class($book); //Make the 'reader' class name and see if it exists $readerClass = $name . 'Reader'; if (class_exists($readerClass)) { //Class exists - yay! Read the book... $reader = new $readerClass($book); $reader->readBook(); } } }
以下是这些类的用法:
$client = new BookClient(); $client->readBook(new MyEBook()); //prints: reading from a MyBook $client->readBook(new MyPDFBook()); //prints: doing what a PDF does while...reading from a MyPDFBook
所有这些看起来都很复杂,只是为了对readBook()进行简单的调用,但获得的灵活性是值得的.例如,稍后客户说“Kindle书籍的支持在哪里?”然后说“马上来!”
interface KindleBook extends EBook { public function doKindleOnlyThing(); } class MyKindleBook extends MyEBook implements KindleBook { public function doKindleOnlyThing() { echo 'waiting FOREVER for the stupid menu to start...'; } } class MyKindleBookReader extends GenericBookReader { public function __construct(KindleBook $book) { parent::__construct($book); } public function readBook() { //You are safe to use KindleBook methods here //because you have a guarantee they are available $this->book->doKindleOnlyThing(); $this->book->read(); } }
扩展示例用法:
$client = new BookClient(); $client->readBook(new MyEBook()); //prints: reading from a MyBook $client->readBook(new MyPDFBook()); //prints: doing what a PDF does while...reading from a MyPDFBook $client->readBook(new MyKindleBook()); //prints: waiting FOREVER for the stupid menu to start...reading from a MyKindleBook
这种使用抽象的特殊设置很好地支持开放/封闭原则.你必须添加一些代码,但你没有改变任何现有的实现 – 甚至不是客户端!
希望这提供了一个额外的角度来查看您的问题.查看您希望设置实现的方式,并开始查看可以抽象出来的内容.有时最好将物体放在彼此的黑暗中,并使用与它们配合使用的特殊处理程序.在这个例子中,没有一本书需要关心另一本书是如何工作的.因此,一个接受任何EBook但具有与该接口的特定子实现一起工作的方法的类最终会成为代码气味.
希望有所帮助.下面是复制和粘贴以完成自己试用的完整示例代码.
<?PHP interface EBook { public function read(); } interface PDFBook extends EBook { public function doPDFOnlyThing(); } interface KindleBook extends EBook { public function doKindleOnlyThing(); } class MyEBook implements EBook { public function read() { echo 'reading from a ' . get_class($this); } } class MyPDFBook extends MyEBook implements PDFBook { public function read() { //you only need to override this method //if needed,otherwise you can leave it //out and default to the parent class //implementation. parent::read(); } public function doPDFOnlyThing() { echo 'doing what a PDF does while...'; } } class MyKindleBook extends MyEBook implements KindleBook { public function doKindleOnlyThing() { echo 'waiting FOREVER for the stupid menu to start...'; } } abstract class GenericBookReader { protected $book; public function __construct(EBook $book) { $this->book = $book; } abstract public function readBook(); } class MyBookReader extends GenericBookReader { public function __construct(EBook $book) { parent::__construct($book); } public function readBook() { $this->book->read(); } } class MyPDFBookReader extends GenericBookReader { public function __construct(PDFBook $book) { parent::__construct($book); } public function readBook() { //You are safe to use PDFBook methods here //because you have a guarantee they are available $this->book->doPDFOnlyThing(); $this->book->read(); } } class MyKindleBookReader extends GenericBookReader { public function __construct(KindleBook $book) { parent::__construct($book); } public function readBook() { //You are safe to use KindleBook methods here //because you have a guarantee they are available $this->book->doKindleOnlyThing(); $this->book->read(); } } class BookClient { public function readBook(EBook $book) { //Get the class name of $book $name = get_class($book); //Make the 'reader' class name and see if it exists $readerClass = $name . 'Reader'; if (class_exists($readerClass)) { //Class exists - yay! Read the book... $reader = new $readerClass($book); $reader->readBook(); } } } $client = new BookClient(); $client->readBook(new MyEBook()); //prints: reading from a MyBook $client->readBook(new MyPDFBook()); //prints: doing what a PDF does while...reading from a MyPDFBook $client->readBook(new MyKindleBook()); //prints: waiting FOREVER for the stupid menu to start...reading from a MyKindleBook