[英]Declaring from Interface type, instead of class type, doesn't seem Polymorphic or more general
[英]Java - declaring from Interface type instead of Class
在我尋求正確掌握界面最佳實踐的過程中,我注意到以下聲明:
List<String> myList = new ArrayList<String>();
代替
ArrayList<String> myList = new ArrayList<String>();
-據我所知,原因是它允許靈活性,以防有一天您不想實現 ArrayList 而可能是另一種類型的列表。
用這個邏輯,我設置了一個例子:
public class InterfaceTest {
public static void main(String[] args) {
PetInterface p = new Cat();
p.talk();
}
}
interface PetInterface {
public void talk();
}
class Dog implements PetInterface {
@Override
public void talk() {
System.out.println("Bark!");
}
}
class Cat implements PetInterface {
@Override
public void talk() {
System.out.println("Meow!");
}
public void batheSelf() {
System.out.println("Cat bathing");
}
}
我的問題是,我無法訪問 BatheSelf() 方法,因為它只存在於 Cat 中。 這讓我相信,如果我只打算使用在接口中聲明的方法(而不是來自子類的額外方法),我應該只從接口聲明,否則我應該直接從類聲明(在這種情況下為 Cat)。 我在這個假設中正確嗎?
當需要選擇通過interface
引用對象還是通過class
引用對象時,應首選前者,但前提是存在適當的類型。
以String
implements
CharSequence
為例。 在所有情況下,您不應該盲目地使用CharSequence
而不是String
,因為這會拒絕您進行簡單的操作,例如trim()
、 toUpperCase()
等。
然而,一個只需要一個String
來關心它的char
值序列的方法應該使用CharSequence
代替,因為在這種情況下這是合適的類型。 實際上replace(CharSequence target, CharSequence replacement)
String
類中的replace(CharSequence target, CharSequence replacement)
就是這種情況。
另一個例子是java.util.regex.Pattern
及其Matcher matcher(CharSequence)
方法。 這使得Matcher
可以從Pattern
創建,不僅用於String
,還用於所有其他CharSequence
。
在庫中應該使用interface
但不幸沒有使用的一個很好的例子也可以在Matcher
找到:它的appendReplacement
和appendTail
方法只接受StringBuffer
。 自 1.5 以來,此類已在很大程度上被其更快的表親StringBuilder
所取代。
StringBuilder
不是StringBuffer
,因此我們不能將前者與Matcher
的append…
方法一起使用。 但是,它們都implements
Appendable
(也在 1.5 中引入)。 理想情況下Matcher
的append…
方法應該接受任何Appendable
,然后我們就可以使用StringBuilder
以及所有其他可用的Appendable
!
因此,我們可以看到,當存在通過接口引用對象的適當類型時,這是一種強大的抽象,但前提是這些類型存在。 如果該類型不存在,那么您可以考慮定義自己的類型(如果有意義)。 例如,在這個Cat
示例中,您可以定義interface SelfBathable
。 然后,而不是引用Cat
,您可以接受任何SelfBathable
對象(例如Parakeet
)
如果創建一個新類型沒有意義,那么你可以通過它的class
來引用它。
如果存在合適的接口類型,則參數、返回值和字段都應使用接口類型聲明。 如果你養成使用接口類型的習慣,你的程序就會靈活得多。 如果不存在適當的接口,則通過類引用對象是完全合適的。
是的,你是對的。 您應該聲明為最通用的類型,提供您使用的方法。
這就是多態的概念。
您是對的,但如果需要,您可以從界面投射到所需的寵物。 例如:
PetInterface p = new Cat();
((Cat)p).batheSelf();
當然,如果您嘗試將您的寵物投給狗,則不能調用 BatheSelf() 方法。 它甚至不會編譯。 因此,為了避免出現問題,您可以使用這樣的方法:
public void bathe(PetInterface p){
if (p instanceof Cat) {
Cat c = (Cat) p;
c.batheSelf();
}
}
使用instanceof
,請確保不會在運行時嘗試讓狗自己洗澡。 這會引發錯誤。
是的,你是對的。 通過讓 Cat 實現“PetInterface”,您可以在上面的示例中使用它並輕松添加更多種類的寵物。 如果您確實需要特定於 Cat,則需要訪問 Cat 類。
您可以從 Cat 中的talk
中調用方法batheSelf
。
通常,與具體類相比,您應該更喜歡接口。 沿着這些思路,如果您可以避免使用 new 運算符(它總是需要一個具體的類型,就像在您的新 ArrayList 示例中一樣),那就更好了。
這一切都與管理代碼中的依賴項有關。 最好只依賴高度抽象的事物(如接口),因為它們也往往非常穩定(請參閱http://objectmentor.com/resources/articles/stability.pdf )。 因為它們沒有代碼,所以只有在 API 更改時才必須更改它們……換句話說,當您希望該接口向世界呈現不同的行為時,即設計更改時。
另一方面,課程一直在變化。 依賴於類的代碼並不關心它如何做,只要 API 的輸入和輸出不改變,調用者就不應該關心。
您應該努力根據開閉原則(參見http://objectmentor.com/resources/articles/ocp.pdf )確定類的行為,這樣即使添加功能,現有接口也不需要更改,您可以指定一個新的子接口。
避免使用 new 運算符的舊方法是使用抽象工廠模式,但這有其自身的一系列問題。 更好的是使用像 Guice 這樣的工具進行依賴注入,並且更喜歡構造函數注入。 在開始使用依賴注入之前,請確保您了解依賴倒置原則(請參閱http://objectmentor.com/resources/articles/dip.pdf )。 我見過很多人注入了不適當的依賴項,然后抱怨這個工具對他們沒有幫助……它不會讓你成為一個偉大的程序員,你仍然必須適當地使用它。
示例:您正在編寫一個幫助學生學習物理的程序。 在這個程序中,學生可以將一個球放在各種物理場景中並觀察它的行為:從懸崖上的大炮中射出它,把它放在水下,在深空等等。 問題:你想包括一些關於球的重量的東西Ball API 中的球……您應該包含 getMass() 方法還是 getWeight() 方法?
重量取決於球碰巧所處的環境。調用者可以方便地調用一個方法並在任何地方獲取球的重量,但是如何編寫這個方法呢? 每個球實例必須不斷跟蹤它的位置以及當前的重力常數是多少。 所以你應該更喜歡 getMass(),因為質量是球的固有屬性,不依賴於它的環境。
等等,如果你只使用 getWeight(Environment) 呢? 這樣,球實例就可以從環境中取出當前的 g 並繼續……更好的是,您可以使用 Guice 在球的構造函數中注入環境! 這是我經常看到的濫用類型,人們最終指責 Guice 無法像他們希望的那樣無縫地處理依賴注入。
這里的問題不是 Guice,而是 Ball API 設計。 重量不是球的固有屬性,因此它不是應該可以從球獲得的屬性。 相反,Ball 應該使用 getMass() 方法實現 MassiveObject 接口,而 Environment 應該有一個名為 getWeightOf(MassiveObject) 的方法。 環境的內在是它自己的萬有引力常數,所以這要好得多。 而 Environment 現在只依賴於一個簡單的接口,MassiveObject……但它的工作是包含對象,所以這應該是。
為什么不簡單地這樣做!
Cat c = new Cat();
PetInterface p = (PetInterface)c;
p.talk();
c.batheSelf();
現在我們只有一個對象,可以使用 2 個引用來操作它。
引用 p 可用於調用接口中定義的函數,而 c 僅可用於調用類(或超類)中定義的函數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.