Java Object Oriented Interview Questions and Answers
Java Object Oriented Interview Questions and Answers
Overly complex design is as bad as no design at all. Get the granularity of your classes
and objects right without overly complicating them. Don't apply too many patterns and
principles to a simple problem. Apply them only when they are adequate. Don't anticipate
changes in requirements ahead of time. Preparing for future changes can easily lead to
overly complex designs. Focus on writing code that is not only easy to understand, but
also flexible enough so that it is easy to change if the requirements change.
Q. Can you explain if the following classes are badly designed?
The following snippets design the classes & interfaces for the following scenario. Bob,
and Jane work for a restaurant. Bob works as manager and a waiter. Jane works as a
waitress. A waiter's behavior is to take customer orders and a manager's behavior is to
manage employees.
view plainprint?
1. package badrestaurant;
2.
3. public interface Person {}
view plainprint?
1. package badrestaurant;
2.
3. public interface Manager extends Person {
4. public void managePeople( );
5. }
view plainprint?
1. package badrestaurant;
2.
3. public interface Waiter extends Person {
4. public void takeOrders( );
5. }
view plainprint?
1. package badrestaurant;
2.
3. public class Bob implements Manager, Waiter {
4.
5. @Override
6. public void managePeople( ) {
7. //implementation goes here
8. }
9.
10. @Override
11. public void takeOrders( ) {
12. //implementation goes here
13. }
14. }
view plainprint?
1. package badrestaurant;
2.
3. public class Jane implements Waiter {
4.
5. @Override
6. public List<string> takeOrders( ) {
7. //implementation goes here
8. }
9. }
view plainprint?
1. package badrestaurant;
2.
3. public class Restaurant {
4.
5. public static void main(String[ ] args) {
6.
7. Bob bob = new Bob( );
8. bob.managePeople( );
9. bob.takeOrders( );
10.
11. Jane jane = new Jane( );
12. jane.takeOrders( );
13. }
14. }
A. The above classes are badly designed for the reasons described below.
The name should be an attribute, and not a class like Bob or Jane. A good OO design
should hide non-essential details through abstraction. If the restaurant employs more
persons, you don't want the system to be inflexible and create new classes like Peter,
Jason, etc for every new employee.
The above solution's incorrect usage of the interfaces for the job roles like Waiter,
Manager, etc will make your classes very rigid and tightly coupled by requiring static
structural changes. What if Bob becomes a full-time manager? You will have to remove
the interface Waiter from the class Bob. What if Jane becomes a manager? You will have
to change the interface Waiter with Manager.
The above drawbacks in the design can be fixed as shown below by asking the right
questions. Basically waiter, manager, etc are roles an employee plays. You can abstract it
out as shown below.
view plainprint?
1. package goodrestuarant;
2.
3. public interface Role {
4. public String getName( );
5. public void perform( );
6. }
view plainprint?
1. package goodrestuarant;
2.
3. public class Waiter implements Role {
4.
5. private String roleName;
6.
7. public Waiter(String roleName) {
8. this.roleName = roleName;
9. }
10.
11. @Override
12. public String getName( ) {
13. return this.roleName;
14. }
15.
16. @Override
17. public void perform( ) {
18. //implementation goes here
19. }
20. }
view plainprint?
1. package goodrestuarant;
2.
3. public class Manager implements Role {
4.
5. private String roleName;
6.
7. public Manager(String roleName) {
8. this.roleName = roleName;
9. }
10.
11. @Override
12. public String getName( ) {
13. return this.roleName;
14. }
15.
16. @Override
17. public void perform( ) {
18. //implementation goes here
19. }
20. }
The Employee class defines the employee name as an attribute as opposed to a class. This
makes the design flexible as new employees can be added at run time by instantiating
new Employee objects with appropriate names. This is the power of abstraction. You
don't have to create new classes for each new employee. The roles are declared as a list
using aggregation (i.e. containment), so that new roles can be added or existing roles can
be removed at run time as the roles of employees change. This makes the design more
flexible.
view plainprint?
1. package goodrestuarant;
2.
3. import java.util.ArrayList;
4. import java.util.List;
5.
6. public class Employee {
7.
8. private String name;
9. private List<role> roles = new ArrayList<role>(10);
10.
11. public Employee(String name){
12. this.name = name;
13. }
14.
15. public String getName( ) {
16. return name;
17. }
18.
19. public void setName(String name) {
20. this.name = name;
21. }
22.
23. public List<role> getRoles( ) {
24. return roles;
25. }
26.
27. public void setRoles(List<role> roles) {
28. this.roles = roles;
29. }
30.
31. public void addRole(Role role){
32. if(role == null){
33. throw new IllegalArgumentException("Role cannot be null");
34. }
35. roles.add(role);
36. }
37.
38. public void removeRole(Role role){
39. if(role == null){
40. throw new IllegalArgumentException("Role cannot be null");
41. }
42. roles.remove(role);
43. }
44. }
The following Restaurant class shows how flexible, extensible, and maintainable the
above design is.
view plainprint?
1. package goodrestuarant;
2.
3. import java.util.List;
4.
5. public class Restaurant {
6.
7. public static void main(String[ ] args) {
8.
9. Employee emp1 = new Employee ("Bob");
10. Role waiter = new Waiter("waiter");
11. Role manager = new Manager("manager");
12.
13. emp1.addRole(waiter);
14. emp1.addRole(manager);
15.
16. Employee emp2 = new Employee("Jane");
17. emp2.addRole(waiter);
18.
19. List<role> roles = emp1.getRoles( );
20. for (Role role : roles) {
21. role.perform( );
22. }
23.
24. //you can add more employees or change roles based on
25. //conditions here at runtime. More flexible.
26. }
27. }
view plainprint?
1. package subclass0;
2.
3. public abstract class Animal {
4. private int id; // id is encapsulated
5.
6. public Animal(int id) {
7. this.id = id;
8. }
9.
10. public int getId( ) {
11. return id;
12. }
13.
14. public abstract void move(Location location);
15. }
view plainprint?
1. package subclass0;
2.
3. public class FarmAnimal extends Animal {
4.
5. private Location location = null; // location is encapsulated
6.
7. public FarmAnimal(int id, Location defaultLocation) {
8. super(id);
9. validateLocation(defaultLocation);
10. this.location = defaultLocation;
11. }
12.
13. public Location getLocation( ) {
14. return location;
15. }
16.
17. public void move(Location location) {
18. validateLocation(location);
19. System.out.println("Id=" + getId( ) + " is moving from "
20. + this.location + " to " + location);
21. this.location = location;
22. }
23.
24. private void validateLocation(Location location) {
25. if (location == null) {
26. throw new IllegalArgumentException("location=" + location);
27. }
28. }
29. }
view plainprint?
1. package subclass0;
2.
3. public enum Location {
4. Barn, Pasture, Stable, Cage, PigSty, Paddock, Pen
5. }
view plainprint?
1. package subclass0;
2.
3. public class Example {
4.
5. public static void main(String[ ] args) {
6. Animal pig = new FarmAnimal(1, Location.Barn);
7. Animal horse = new FarmAnimal(2, Location.Stable);
8. Animal cow = new FarmAnimal(3, Location.Pen);
9.
10. pig.move(Location.Paddock);
11. horse.move(Location.Pen);
12. cow.move(Location.Pasture);
13. }
14. }
Output:
In the above example, the class FarmAnimal is an abstraction used in place of an actual
farm animal like horse, pig, cow, etc. In future, you can have WildAnimal, CircusAnimal,
etc extending the Animal class to provide an abstraction for wild animals like zebra,
giraffe, etc and circus animals like lion, tiger, elephant, etc respectively. An Animal is a
further abstraction generalizing FarmAnimal, WildAnimal, and CircusAnimal. The
Location is coded as an enumeration for simplicity. The Location itself can be an abstract
class or an interface providing an abstraction for OpenLocation, EnclosedLocation, and
SecuredLocation further abstracting specific location details like barn, pen, pasture,
pigsty, stable, cage, etc. The location details can be represented with attributes like
“name”, “type”, etc.
The FarmAnimal class is also well encapsulated by declaring the attribute “location” as
private. Hence the “location” variable cannot be directly accessed. Assignment is only
allowed through the constructor and move(Location location) method, only after a
successful precondition check with the validateLocation(...) method. The
validateLocation(...) itself marked private as it is an internal detail that does not have to
be exposed to the caller. In practice, the public move(..) method can make use of many
other private methods that are hidden from the caller. The caller only needs to know what
can be done with an Animal. For example, they can be moved from one location to
another. The internal details as to how the animals are moved is not exposed to the caller.
These implementation details are specific to FarmAnimal, WildAnimal, and
CircusAnimal classes.
Some of the above questions are answered in How to write immutable Java classes?