views:

179

answers:

7

This question was asked to me in MS interview. I wanna know the exact design issue in this piece of code. Code was already given, needed to find the design issue.

I have class MyHashMap which extends java HashMap class. In MyHashMap class I have to keep some information of employees. Key in this map will be 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 
}

What is wrong with this design? Should I subclass HashMap or should I declare HashMap as member variable of this class? Or should I have implemented hashCode/equals methods?

+3  A: 

I would go for declaring HashMap as member variable of your class.

I don't remember if a great explanation is given in Clean Code or Effective Java, but basically, it's simplier to do, requires less work if the API changes.

generally, extending such a class means you want to change its behavior.

chburd
Same.. i would prefer aggregation over sublcassing
daedlus
+10  A: 

There are quite a few problems, but the biggest problem I can see it that you're using a concatenated String as a key. The following two calls are different, but equivalent:

final MyHashMap map = new MyHashMap();

map.put("foo", "", "baz", new Object());
map.put("", "foo", "baz", new Object()); // Overwrites the previous call

There's also an issue that you're declaring that the key type as an Object, but always using String and are therefore not taking advantage of the type safety that comes with generics. For example, if you wanted to loop through the keySet of your Map, you'd have to cast each Set entry to a String, but you couldn't be sure that someone didn't abuse you Map by using an Integer key, for example.

Personally, I would favour composition over inheritance unless you have a good reason not to. In your case, MyHashMap is overloading the standard Map methods of put, get and remove, but not overriding any of them. You should inherit from a class in order to change its behaviour, but your implementation does not do this, so composition is a clear choice.

To act as an example, overloading rather than overriding means that if you make the following declaration:

Map<Object, Object> map = new MyHashMap();

none of your declared methods will be available. As recommended by some of the other answers, it would be far better to use an object composed of firstName, lastName and address to act as your map key, but you must remember to implement equals and hashCode, otherwise your values will not be retrievable from the HashMap.

David Grant
+4  A: 

What's wrong with that design is primarily that is claims to be a HashMap<Oject, Object>, but isn't really. The overloaded methods "replace" the Map methods, but those are still accessible - now you're supposed to use the class in a way that is incompatible with the Map interface and ignore the (still technically possible) compatible way to use it.

The best way to do this would be to make an EmployeeData class with name and address fields and hashCode() and equals() methods based on those (or, better yet, a unique ID field). Then you don't need a non-standard Map subclass - you can simply use a HashMap<EmployeeData, Object>. Actually, the value type should be more specific than Object as well.

Michael Borgwardt
+1: The Liskov Substitution Principle should be followed in any OO language.
Christopher Creutzig
+2  A: 

My first take would be that:

public MyHashMap extends HashMap<Oject, Object>

is wrong. No type safety.

If a Map is required then at least make it

public MyHashMap extends HashMap<NameAddress, Employee>

and adjust the put and get the same. A NameAddress use firstname, lastname and address in the equals and hashCode.

I personally feel that a Set interface would match better, with possibly a HashMap as a member. Then you could define the interface like it should be:

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) {
  }
}

and extract name and address info within the implementation to create the key.

EDIT David pointed out that NameAddress doesn't need to be Comparable and I removed that part of the interface. Added hashCode for correctness.

extraneon
Why make NameAddress comparable? HashMap doesn't implement SortedMap.
David Grant
@David You are right, I'll update.
extraneon
A: 

I think it depends a lot on the context and what they exactly asked. For example, is a highly concurrent context, you should have used Hashtable instead. Also, you should specify the types (String, Object) instead of (Object,Object) to get compiler support on the keys.

I think a better design would have been to implement the Map interface and keep the HashMap/HashTable as an internal parameter of the object.

Pere Villega
+2  A: 

The map uses an internal key based on first name, last name and address. So the remove method should (1) be implemented as remove(String firstName, String lastName, String Address) and (2) it should not change the behaviour of the original remove method (which really deleted the entry) by just changing the value. A better implementation of remove would be:

public void remove(String firstName, String lastName, String address) {
   String key =   firstName + lastName + address;
   return remove(key);
}
Andreas_D
2) It doesn't change the behaviour of the original remove method, because it doesn't override it.
David Grant
Sure - but the question asked for bad design. And it is pretty bad to use the 'remove' method name for something completely different (this remove method doesn't remove anything).
Andreas_D
+1  A: 

Two other "heinous crimes" against good practice are that the remove method:

  • overloads Map.remove(<K>) rather than overriding it (so you have a leaky abstraction), and

  • implements a behavior that is incompatible with the behavior of the method it overrides!

Setting the value associated with a key to an empty string is NOT THE SAME as removing the entry for the key. (Even setting it to null is subtly different ...)

This is not a violation of the Liskov substitutability principle, but it could be really confusing for someone trying to use the type. (Depending on whether you called the method via the Map type or the MyHashMap type you could end up using different methods with different semantics ....)

Stephen C