The major advantages to Object Orientation are encapsulation and inheritance.
We have already discussed encapsulation when we discussed the class Tank. An object or an instance of class tank not only has the content of a tank held inside it, it has the methods that operate on or query the content inside it as well. This way, no matter where in the application that uses tanks trys to add or remove liquid, we can be sure content is never left in an abnormal state. Remember that we throw exceptions if the application attempts to over fill the tank or take too much out of the tank. This simple solution saves a great deal of our debugging time, as we would not have designed tank this way if we were programming imperatively with side-effects. We would have probably had a content variable for each tank maintained and would have increased or decreased its content with assignment statements right in the application. So, say the content has become negative due to a bad operation, most often, we wouldn't figure that out until much later. These bugs used to be difficult to find and very time consuming. The way that tank is designed here enables us to detect the error where it happens. I want to make it clear that this doesn't mean we were stupid before and with object orientation we have become smart. It simply means that we avoid the whole issue of forgetting to check for content having become instable, the object itself won't let you forget by throwing an exception.
So, it is very useful for tank objects to protect their content. We have achieved that by encapsulating data and methods, including exception handling mechanism that we created for tank in each instance/variable/object (any of those names are ok here) of class Tank.
We first talked about Inheritance when we discussed the exception handling features of Java. Recall that in Java we create exception classes such as TankOverFlowException that are subclasses of a super class called Exception. It is not hard to think of why super classes such as Exception are useful. For one thing, it provides uniformity to the way we handle abnormal cases. Any of our methods that throw an exception force the application that invokes them to catch that exception. This is something that if the application programmer forgets to deal with, he or she will be reminded with a compilation error. Since all exceptions are designed in similar fashion, we have a sense of familiarity when we look at new classes that throw them. That feeling of familiarity is part of the draw for object orientation, it prevails all over the place.
displayTank is a subclass of the Tank class. As the class
header indicates, displayTank extends Tank (public class displayTank extends Tank
).
displayTank simply adds a new functionality to the Tank class that enable us to display the Tank at some coordinate of a canvas. You should notice that displayTank does not extend canvas; thats why it has a display method instead of a paint method that the trafficLight class had. The display method will need a Graphic object as a parameter to draw on, but, unlike the paint method it must be called explicitly. You will see later that it is called from the tankApplet's paint method.
The key point to be made here is that all fields and methods of Tank are inherited by displayTank as if we had literally copied them from Tank.
The call to super in the displayTank's constructor is for the invokation of the constructor in Tank, setting of the content_ to zero in Tank gets performed because of this call. Other than that, the constructor sets the x and y coordinates of the tank.
The display as mentioned before draws the tank on the applet's canvas.
snapshot of the tankApplet shows the components displayed. Four buttons on top, a tank in the middle, and a textField at the bottom. tankApplet introduces a different layout manager as well, BorderLayout enable us to place components at borders of the applet's canvas (i.e. North, South, etc.). This applet is also used to introduce the use of Panel that enable us to group components together. In this applet, the four buttons are placed in panel and the panel is placed at the north boarder of the applet's canvas.
Of course, t, our tank, is not added in the init method like other components because it is not one. If you look at the paint method here you will notice the call to t's dislplay method; this what I ment earlier about the need for an explicite call to the display method in order to draw the tank. The only safe place to call the display method is from the paint method of an applet or some other Canvas subclass.
The action method handles the button clicks and displays messages
in the textField. Notice that t.add(amt)
or
t.remove(amt)
calls are made as if t is just a regular
tank; keep in mind that displayTank "is a" Tank.
You will develop a changableTank class which extends displayTank. changeableTank adds the functionality of switching the size of a tank from large to small or small to large, as well as, providing a method that would reposition the tank at a different cooridinate. the changeableTankApplet works similarly to tankApplet except that it tests the repositioning and size-switching of the tank by providing a couple of buttons for it.
There are numerous application for either of these data structures. For example, you issue a print command on a multi-user system, is it not possible for other people to also issue the same command? A queue allows us to process these print requests in order. An example of where a stack is useful is in implementation of a preemption mechanism for resources. Lets consider a single cpu system with multiple processes competing for access to cpu. Often we assign priority to these requests. Suppose we have 4 priority levels, 1 through 4. Now, assume a process with the lowest priority (1) takes over the cpu and is processed for a while. Suppose that a new process comes along with priority 2, it preempts the first process by taking over the cpu, but the system can't throw away the first process, so it is put on a stack. Consider a process with priority 3 coming along and preempting the process with priority 2, guess what happens to the preempted process? Yes, it gets put on the stack, but because of the behavior of the stack, when the priority 3 process is done, it is a "no brainer" as to who should be picked from the ones preempted. The one with the highest priority will be at the top and it is the one we take off and resume processing of since the data structure is a stack.
There are variety of implementations for stacks and queues. For a queue,
we can define an array and put each element that comes in in the next
available spot. When it is time to take an element off, we take the element
in cell zero and move the rest back.
We can implement a circular queue where we keep two
indexes one to the front of the queue and one to the back. In such a
implementation, we don't move everything back when an element is taken off--we
just move our index over to the next cell. But, its more complicated, as the
name indicates when we get to the last spot in the array we circle back to
the beginning. This makes adding and removing a little more complex, but
the advantage is in that you don't have to move everything back, each time
that you remove an element. The last implementation is a linked list. If
we don't know how much room we need to put aside for our queue, we
implement it with a linked list that can grow indefinitely!
Here is a stack and a queue class that hold Objects.
Note that they both throw Exception objects when dealing with putting elements in when there is no room or removing elements when there is nothing to remove.
public class Stack {
private int size_;
private int top_=-1;
private Object l_[];
public Stack (int size) {
size_ = size;
l_=new Object[size];
}
public boolean empty() {
return top_ == -1;
}
public boolean full(){
return top_ == size_-1;
}
public Object pop() throws Exception {
if (empty()) throw new Exception();
top_--;
return l_[top_+1];
}
public void push(Object val) throws Exception {
if (full()) throw new Exception();
l_[++top_]=val;
}
}
public class Queue {
private int size_;
private int last_=-1;
private Object q_[];
public Queue (int size) {
size_ = size;
q_=new Object[size_];
}
public boolean empty() {
return last_ == -1;
}
public boolean full() {
return last_ == size_-1;
}
public void enqueue(Object x) throws Exception {
if (last_ == size_ - 1) throw new Exception();
q_[++last_]=x;
}
public void dequeue() throws Exception {
if (last_ == -1) throw new Exception();
for (int i=0;i<last_;i++) q_[i]=q_[i+1]; //shift elements back by 1 cell.
}
public Object peek() throws Exception {
if (last_ == -1) throw new Exception();
return q_[0];
}
}