[英]What is wrong with this design? Overriding or overloading of java.util.HashMap
這個問題在MS面試時被問到了。 我想知道這段代碼中的確切設計問題。 已經提供了代碼,需要找到設計問題。
我有類MyHashMap,它擴展了Java HashMap類。 在MyHashMap課程中,我必須保留一些員工的信息。 此映射中的鍵將是firstName + lastName + Address。
public MyHashMap extends HashMap<Object, Object> {
//some member variables
//
public void put(String firstName, String lastName, String Address, Object obj) {
String key = firstName + lastName+ Address;
put(key, obj);
}
public Object get(String firstName, String lastName, String Address) {
String key = firstName + lastName+ Address;
return get(key);
}
public void remove(Strig key) {
put(key, "");
}
//some more methods
}
這個設計有什么問題? 我應該將HashMap子類化還是應該將HashMap聲明為此類的成員變量? 或者我應該實現hashCode / equals方法?
有很多問題,但最大的問題是我可以看到你使用串聯String
作為鍵。 以下兩個調用是不同的,但相當於:
final MyHashMap map = new MyHashMap();
map.put("foo", "", "baz", new Object());
map.put("", "foo", "baz", new Object()); // Overwrites the previous call
還有一個問題是,您將關鍵類型聲明為Object
,但始終使用String
,因此不會利用泛型帶來的類型安全性。 例如,如果你想循環遍歷Map
的keySet
,你必須將每個Set
條目轉換為String
,但你不能確定有人沒有使用Integer
鍵濫用你的Map
,例。
就個人而言,除非你有充分的理由不這樣做,否則我會贊成合成而非繼承 。 在您的情況下, MyHashMap
重載了put
, get
和remove
的標准Map
方法,但沒有覆蓋它們中的任何一個。 您應該從類繼承以更改其行為,但您的實現不會這樣做,因此組合是一個明確的選擇。
作為一個例子,重載而不是覆蓋意味着如果你做出以下聲明:
Map<Object, Object> map = new MyHashMap();
沒有任何聲明的方法可用。 根據其他一些答案的建議,使用由firstName,lastName和address組成的對象作為你的map鍵會好得多,但是你必須記住實現equals
和hashCode
,否則你的值將無法從HashMap
。
該設計的主要原因是聲稱是HashMap<Oject, Object>
,但實際上並非如此。 重載方法“替換”了Map
方法,但仍然可以訪問它們 - 現在你應該以與Map
接口不兼容的方式使用該類,並忽略(在技術上可行)兼容的方式來使用它。
執行此操作的最佳方法是使用名稱和地址字段以及基於這些的hashCode()
和equals()
方法創建EmployeeData
類(或者更好的是,唯一的ID字段)。 那么你不需要非標准的Map子類 - 你可以簡單地使用HashMap<EmployeeData, Object>
。 實際上,值類型也應該比Object
更具體。
我會將HashMap聲明為您班級的成員變量。
我不記得是否在Clean Code或Effective Java中給出了很好的解釋,但基本上,它更簡單,如果API發生變化則需要更少的工作。
通常,擴展這樣的類意味着你想改變它的行為。
我的第一個看法是:
public MyHashMap extends HashMap<Oject, Object>
是錯的。 沒有類型安全。
如果需要的地圖,則至少使它
public MyHashMap extends HashMap<NameAddress, Employee>
並調整看跌期權並獲得相同的信息。 NameAddress使用equals和hashCode中的firstname,lastname和address。
我個人認為Set接口會更好地匹配,可能還有HashMap作為成員。 然后你就可以定義界面了:
public EmployeeSet implements Set<Employee> {
private static class NameAddress {
public boolean equals();
public int hashCode();
}
private HashMap employees = new HashMap<NameAddress, Employee>();
public add(Employee emp) {
NameAddress nad = new NameAddress(emp);
empoyees.add(nad, emp);
}
public remove(Employee emp) {
}
}
並在實現中提取名稱和地址信息以創建密鑰。
編輯 David指出NameAddress不需要是Comparable,我刪除了該部分接口。 為正確性添加了hashCode。
地圖使用基於名字,姓氏和地址的內部密鑰。 所以remove
方法應該(1)實現為remove(String firstName, String lastName, String Address)
和(2)它不應該通過改變值來改變原始remove方法(它真正刪除了條目)的行為。 更好的刪除實現將是:
public void remove(String firstName, String lastName, String address) {
String key = firstName + lastName + address;
return remove(key);
}
另外兩個反對良好做法的“令人發指的罪行”是remove
方法:
重載Map.remove(<K>)
而不是覆蓋它(所以你有一個漏洞的抽象),和
實現與它重寫的方法的行為不兼容的行為!
將與鍵關聯的值設置為空字符串與刪除鍵的條目不同。 (即使將其設置為null
也略有不同......)
這並不違反Liskov可替代性原則,但對於試圖使用該類型的人來說,這可能會讓人感到困惑。 (根據您是通過Map
類型還是MyHashMap
類型調用方法,您最終可能會使用具有不同語義的不同方法....)
我認為這在很大程度上取決於背景以及他們究竟要求的內容。 例如,是高度並發的上下文,您應該使用Hashtable。 此外,您應指定類型(String,Object)而不是(Object,Object)以獲得對鍵的編譯器支持。
我認為更好的設計是實現Map接口並將HashMap / HashTable保持為對象的內部參數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.