#typedef CustomerList vector<Customer&>;
The reason C gave us typedef is to simplify the type names used. Simplifying things reduces the mistakes that are made.
Fast forward to Java and C#: they don't have typedef! No, they don't, but I can still make this work with class inheritance. In fact, you should choose inheritance over typedef in C++ too, we'll see why in a moment. Here is a Java class that does nothing more than create a new "CustomerList type" from an ArrayList:
class CustomerList extends ArrayList<Customer> {
}
}
So why make this class? Well, one reason is that I'm basically lazy and I just don't want to type ArrayList<Customer> all the time. So the immediate effect is that the clients using this will not have to keep repeating a generic type. It's harder to make dumb mistakes if I just have to type CustomerList.
When I teach any object-oriented programming I always stress design to the abstraction. Sometimes I'll call it "code to the interface." Let's say that we have a class with a method that returns an ArrayList<Customer>. When I store that value I'll save it in a variable of type List<Customer>, because I only need the methods the List interface provides. The method could change and return something other than an ArrayList. But as long as whatever it returns still implements List<Customer> I'll be able to use it without modifying my code. Here's a brief example of what that might work like:
I can't stress enough that the Customers shouldn't reveal anything more than what the clients need. So if the class uses an ArrayList<Customer> internally, then the getCustomers method really should return List<Customer>. "What you can hide you can change," so if the class hides that it is really an ArrayList it can be changed without affecting any clients. Let's make that change to line seven:
But it's still not complete. Whenever a class uses a collection internally a choice has to be made: do we give clients access to the list and the elements or not? If we want to protect the list, then we have another question to answer: where do we put the code to protect the list?
If I put the code in the Customers class then I'm sure to violate the Single Responsibility Principle (Martin). The Customers class already has the responsibility of managing the collection of customers, so the code to protect the list is a second responsibility. We don't want to mix responsibilities in a class if we can avoid it, that breaks cohesion in the class.
So when I created the abstraction CustomerList that happens to be a class that doesn't have any responsibilities. It's the perfect place to put the code to protect the list! We'll simply make Customers return a new CutomerList based on the private ArrayList. To do that it will have a constructor that can accept the ArrayList and duplicate it. Fortunately there is already a constructor in the superclass that implements it for us.
But the new CustomerList still has references to all of the original Customers in the ArrayList. To prevent the original Customer instances from being modified we'll lazily make a clone and return that when a customer is retrieved:
So making the extra class is a balancing act, and here's where I draw the line:
When I teach any object-oriented programming I always stress design to the abstraction. Sometimes I'll call it "code to the interface." Let's say that we have a class with a method that returns an ArrayList<Customer>. When I store that value I'll save it in a variable of type List<Customer>, because I only need the methods the List interface provides. The method could change and return something other than an ArrayList. But as long as whatever it returns still implements List<Customer> I'll be able to use it without modifying my code. Here's a brief example of what that might work like:
class Customers {
@tab;private ArrayList customers;
@tab;public Customers() { customers = new ArrayList(); }
@tab;public ArrayList getCustomers() { return customers; }
}
...
Customers customerManager = new Customers();
List customerList = customerManager.getCustomers();
@tab;private ArrayList
@tab;public Customers() { customers = new ArrayList(); }
@tab;public ArrayList
}
...
Customers customerManager = new Customers();
List
I can't stress enough that the Customers shouldn't reveal anything more than what the clients need. So if the class uses an ArrayList<Customer> internally, then the getCustomers method really should return List<Customer>. "What you can hide you can change," so if the class hides that it is really an ArrayList it can be changed without affecting any clients. Let's make that change to line seven:
public List<Customer> getCustomers() { return customers; }
But it's still not complete. Whenever a class uses a collection internally a choice has to be made: do we give clients access to the list and the elements or not? If we want to protect the list, then we have another question to answer: where do we put the code to protect the list?
If I put the code in the Customers class then I'm sure to violate the Single Responsibility Principle (Martin). The Customers class already has the responsibility of managing the collection of customers, so the code to protect the list is a second responsibility. We don't want to mix responsibilities in a class if we can avoid it, that breaks cohesion in the class.
So when I created the abstraction CustomerList that happens to be a class that doesn't have any responsibilities. It's the perfect place to put the code to protect the list! We'll simply make Customers return a new CutomerList based on the private ArrayList. To do that it will have a constructor that can accept the ArrayList and duplicate it. Fortunately there is already a constructor in the superclass that implements it for us.
But the new CustomerList still has references to all of the original Customers in the ArrayList. To prevent the original Customer instances from being modified we'll lazily make a clone and return that when a customer is retrieved:
class CustomerList extends ArrayList {
@tab;public CustomerList(Collection c) { super(c); }
@tab;public void add(Customer c) { throw new CustomerListException(); }
@tab;public Customer get(int index) { return super.get(index).clone(); }
@tab;...
}
...
class Customers {
@tab;private ArrayList customers;
@tab;public Customers() { customers = new ArrayList(); }
@tab;public CustomerList getCustomers() { return new CustomerList(customers); }
}
@tab;public CustomerList(Collection c) { super(c); }
@tab;public void add(Customer c) { throw new CustomerListException(); }
@tab;public Customer get(int index) { return super.get(index).clone(); }
@tab;...
}
...
class Customers {
@tab;private ArrayList
@tab;public Customers() { customers = new ArrayList(); }
@tab;public CustomerList getCustomers() { return new CustomerList(customers); }
}
So making the extra class is a balancing act, and here's where I draw the line:
- When I'm using the Java API or a third party library I simply use the highest abstraction I can for what the library gives me. I don't create new types to wrap what they use.
- When I'm working with my own classes and what I'm returning to a client is getting large (to type) or confusing then I'll create a class as an abstraction between that code and the client. If I create one of these classes then I create the abstractions in every instance to keep my project code symmetrical.
No comments:
Post a Comment