![]() | Monkeys at Keyboards: The Javanomicon © Michael James Heron | ||||
| Topic: Java Programming Level: 2 Version: delta | |||||
10 - Object Oriented Programming | |||||
| Previous | Table of Contents | Next |
| Forum |
| Chapter Objectives |
By the end of this chapter, the reader will be able to:
|
We have been using objects and classes all the way through this book, but we have not yet actually taken the time to look at what they are in theory and in practice. We shall address this deficiency in this chapter. Objects and classes serve as the fundamental building blocks of modern OO programming languages - hardly surprising when we consider that OO stands for Object Orientation. The concept can be quite difficult to understand at the beginning - regardless of many texts that argue to the contrary, it is not a natural way for people to think or analyse problems. Object orientation involves breaking a problem down into separate components in a way that is contradictory to how we would approach such a thing in our minds. Despite this, object orientation is a powerful programming paradigm and the tools it provides can lead to the development of richer and more elegant programming solutions than is possible with other, earlier development frameworks.
Simply speaking, an object is a thing. I know, this a definition that is so vague as to be insultingly useless. So to state it a little more solidly:
Even this doesn't explain what an object is - for something that is so fundamental to the idea of modern programming, the definition of an object is always uncomfortably woolly. In programming terms, an object is an instance of a class. A class is a 'blueprint' or a 'template' that indicates what attributes and behaviours a particular object has. The distinction here is subtle and we won't spend an awful lot of time going into the specifics of classification theory. Let's have a look at a simple, abstract example. Let us posit the existence of a theoretical class, human face. We know what a human face is when we look at it - there are certain pieces of similarity that mean we can make an abstract reference to such a class independent of any specifics. Each human face has a number of attributes -
The class human face gives us an indication of the things that human faces share. The class may also have some behaviours that go with it:
These are behaviours that are shared by all human faces. An object is an instantiation of a class. This is a grand way of saying 'it's a particular instance of a class'. So we have the class human face that defines what attributes a particular face has, and we can have an object (for example, Phillip's face) that determines the state of those particular attributes.
Likewise we may also have an object Erika's face:
Both of these objects share a degree of similarity in that they have the same attributes and behaviours. The object is what determines what the state of these attributes is going to be.
We have already used this idea in the code we've been writing, although we've ignored it in favour of actually getting some programs written without becoming bogged down in reams of complex theory. In this chapter, we'll actually look at what's going on when we write a Java program. Consider Our First Event Driven Applet from chapter four:
We will again ignore the lines that say import for now, and concentrate instead on what is known as the class definition - this is the part of the code that tells Java what your program is going to be called:
We will look at what the word public means in the next chapter. The word we're interested in at the moment is the second word - class. This indicates to Java that what we're writing is a class. In this case, the class is somewhat complicated since we're making use of the functionality provided by an existing Java library - more on this later. Let's look at a simpler example - a class that simply holds a person's name and their age:
Here, we have defined that there is a class called Person, and it has the attributes name and age. Every instance of a class maintains its own state, and so has its own values for these attributes. A class also often has behaviours that go along with it, and often these behaviours are based on the data stored in a given object. We can provide methods for our classes that allow us to update and query the state of an object:
These methods act on the attributes of a class, but as has been indicated above, it is only when we have an instance of a class that these attributes have an actual state. We have provided some methods that can be used on our attributes, but for these particular methods to work we require an actual object to be instantiated. We will look at this in a few moments. Our class can have objects of its own as attributes - this is a process called assosciaton, as in an object is assosciated with other objects. If we wanted, we could easily have an attribute of type Person within our class... for example, we might want access to the father and mother of a particular person. This would let us easily set up a family tree structure:
Any object of any level of complexity is a candidate for inclusion within a class, and the more complex your program is, the more likely that you classes will consists of many different objects interacting in many different ways.
The class we have just created is very simple - trivial, even. It's a springboard for starting development of what may be a very substantial piece of functionality. At the moment, we can tell at a glance what methods exist and what attributes our class has. It's unlikely that we'll be able to do the same thing when the class has fifty methods and twenty attributes. There are a wide range of documentation standards that are intended to aid in the communication of the details of a class structure. The one we're going to use in this book is from a complex analysis and design protocol called UML (Unified Modelling Language). UML is often used in the design of object oriented programs... as a discipline it is far too complex for us to cover here. However, like pirates aboard a captured vessel, we'll take what we want and ignore the rest. UML provides a diagramattic notation called a class diagram. This succintly represents what methods a class has, what attributes it has, and the relationship the class has to other objects. At the moment, we only know about object Association... in the next chapter we'll also learn about inheritance. The simplest representation of a class diagram shows only the relationship between different classes. It does not show internal primitive variables, and usually does not show internal object attributes that come from the standard Java API. Instead, it shows the relationship between classes that are local to a particular project. UML is comprised of different symbols. For an abstract view of a class relationship, a class is represented simply by a box that contains its name. Relationships between different classes are represented by variations of lines. A filled line between two classes indicates an association. An arrow on the end of that line indicates ownership. For example:
This indicates a cyclic relationship, as in a Person has attributes of Person. Let's consider another example where a class Customer has attributes of type Order:
We can also indicate cardinality on the diagram. A Person will have exactly two parents. A customer may have any number of orders... this is represented by a n symbol, or by a star. So for our Person class:
And then for our Customer and Order classes. This is called a one to many relationship. An order will only ever belong to one customer, but a customer may have many orders.
This kind of class diagram gives a very simple abstract view of how classes interact, but it's not very useful. Usually we expand the class notation to include information on the name of the class, the class attributes, and the class methods... in effect, we expand the one box symbol into a three box symbol. The first box contains the class name, the second box contains the attributes, and the third box contains the methods. Attributes are listed with their name first, and then their type separated by a colon. Methods are shown the same way, except that they have their parameters indicated in brackets after their name:
This allows us to tell, at a glance, what attributes and what methods are available within a class. There's another element of the notation that we haven't discussed yet, but we'll get to that in the next chapter. Consider our second example class diagram. From the first, we knew what the code was and so the diagram only shows what we already know. This underlines a key point about class diagrams - they're not really for you as a developer. They're for the poor souls who have to use your code. Let's say you were given the Customer and Order classes... you haven't written them, and you don't know what the code is. It's difficult to know how they work without some kind of documentation, and that's what the class diagram provides us:
Now with this diagram, we know at a glance what methods there are and what they return. We also know how the classes link together and what methods we have to manipulate the attributes. We'll have cause to return to these kind of diagrams throughout the course of the book. Stay tuned!
We can use the special Java keyword static to declare a method as being a class method as opposed to an object method. We've already seen the use of static methods in our code, such as with Integer.parseInt. The parseInt method does not require an object to be created before it can be used - it is entirely a utility method that can function purely on the basis of the information passed into it. Static methods can be called on the class itself. Consider the following example class definition:
In this example, we can call the addTwoNumbers method without having to create an object first. For example, in the actionPerformed method of a JApplet:
However, in order to use the non-static method (subtractTwoNumbers), we have to declare an object first:
Static methods are more limited than object methods - they can only make use of other static methods in a class, and call upon static variables (more on these in a second). For example, consider this simple console application:
This program won't even compile, because the static method main is attempting to make use of a non-static variable i, and a non static method incrementNumber. Both of these need to be declared as static in order for the program to compile:
The name static is somewhat misleading, and is a holdover from C++ terminology. There is nothing static about them, so when applied to variables it doesn't mean that the contents remain the same. It only has meaning as far as ownership within the class/object paradigm. Static variables are like static methods in that they belong to a class, rather than to an object. The consequence of this is that all objects share the same variable. For example, we could use a static variable to keep track of all the instances created of a particular class:
And:
Although we create two instances of this class, because the getNumber method returns the state of a static variable, the output is:
We'll have cause to see an occasion or two when the use of static methods and variables may be useful in a future case study.
We can provide any range of methods within our class - we are limited only by our imagination and the restrictions of Java itself. However, there is a special family of methods that exists in classes - this methods of this family are known as constructor methods. Constructor methods are used to setup the initial state of an object, and they get called as soon as an object is created from a class definition. These methods always have the same name as the class, and have no return type. This doesn't mean that they return void, it means that they have absolutely no return type at all. Constructor methods can be overloaded like any other method to provide a range of possible strategies for setting up the initial state of an object - see chapter two for an overview of method overloading. We do not have to write a constructor method for our class - Java will provide for us one by default. The default constructor has an empty parameter list and does nothing except ensure that the object can indeed be created properly. This acts as a safety net of sorts - Java creates it so that you don't need to worry about this when developing your own classes. However, if you create a constructor method of your own, this safety net will be removed - once you have written your own constructor method then only the constructor methods you provide will be available - Java will not provide a default one. So, let's add a constructor method for our Person class - it will be one that lets us pass both the name and age into the constructor to setup the state of the object:
Now when we create an instance of this class, we need to pass in a string and an integer as parameters:
So, now we've looked at writing classes and constructors, let's look at how we actually start to do something with them.
A class is nothing (at this stage of our development) unless we can instantiate an object from it - we've already done this with many of our Swing components. We instantiate an object using the new keyword. When we use the new keyword to create an object, Java looks for a .class file that matches the name we're requesting. When it finds it, it looks through the class for a constructor method that matches the parameter list we pass in. If it doesn't find a matching constructor, it will give an error message. If it does find a matching constructor , it will call the appropriate constructor method and then place the configured object into the memory location we set up:
This particular instantiation works only because we've created an appropriate constructor in our class:
If we didn't have this constructor method, we would be still be able to create an object thusly:
This allows us to create an object, but leaves the initial starting state of its attributes untouched. This is often desirable behaviour, so we can overload our constructor method to provide just such a constructor in addition to the one we have written above:
Now with our class we can create an object using one of these two constructors:
Either of these syntaxes will work, because we have provided appropriate constructor methods to deal with either parameter list. We usually include any custom-written constructor methods in the method box of a class-diagram. If we have none, we don't include the default constructor - its existence is infered.
We've got our object, but what can we actually to do with it? The benefit of using an object is that it essentially allows us to write our very own data type - a data type that we can use to group together contextually related chunks of data into one coherent unit. However, we can also provide behaviours that let our object perform sophisticated (or not so sophisticated) calculations on the data we have stored within. So, now that we have our object we want to be able to access the variables it stores, and the methods. We do this using the dot (.) operator that we have used previously with our swing components, objects of the String class, and more:
We can access object variables (more on this later) using the dot notation also:
For a number of reasons, this isn't a good idea - we'll look at why this is the case later in this chapter.
Perhaps the most difficult part about object oriented programming is working out what objects you need, and what pieces of code should go where. For the most part, this is something that comes with practise and cannot easily be taught. However, there is a useful design philosophy that can be used to good effect to at least break your development projects into manageable chunks - this is called the model view controller architecture. Essentially, this technique breaks program development into the writing of classes to meet three requirements of functionality:
So far, we have combined all three of these rough categories into a single applet - from this point on, we'll look at separating out the model from the view and the controller (which will still be handled by the applet itself). At its simplest, a model may be shared between many view/controllers, and a view/controller will make use of one model. Its possible for the view/controller to have many models, but we'll just think about it having one for now:
Why should we do this? The answer comes down to good object design. Consider the following situation: You've been a good, busy developer and written an excellent applet that generates a list of random quotes and allows the user to step through them. The user can request quotes by author, by subject, or just completely randomly. You deploy your applet on a website, and Mister Megabucks, chairman of We Give You Money Incorporated, comes along and says to you 'Son, we love your quote applet. We'd really like to buy it off of you so we can use it in our own projects'. You rejoice as he starts to write out the cheque, which contains enough zeros to make your head spin. 'Here you go, Mister Megabucks' you say, and hand him your applet code. 'Son, what's this?', he says. 'That's my applet', you say. 'That's no good to me... I don't want it as an applet. I just want the quote generator part. We'll do our own interface'. 'But the quote generator is part of the applet', you protest. 'You can just copy and paste the relevant bits into your own program'. 'Son, the code is too closely tied to the interface', says Mister Megabucks. 'If I want to use this in my own application, then I need to find out how your generator interfaces with your GUI components, and then extract them all, move them into my program, and then tie them up in there. That's too much work. I can hire a developer to write a generator from scratch for less than I am paying you!'. With that, he tears up his cheque and storms out of the room. You slump, dejected, into your favourite arm-chair. You refuse all food, all water, until eventually they have to call some fire-men to sand-blast your dessicated corpse off of the sofa. The problem with placing all of the code into a single class is that it leads to bad object design. Let's say that Mister Megabucks had been a kinder soul and agreed to copy and paste your code into his application. And then let's say ten other billionaires bought your software and copied it into their applications. And then one hundred more. And then, one fateful day, you find a horrible bug - if the author of the quote has a D in their name and it's a Wednesday, then the computer will catch fire and explode, killing everyone within a ten mile radius. It must be fixed! How do you fix it? Well, you could get everyone who bought the code off of you to implement their own fix... that's unlikely to be popular. Generally people don't take kindly to having to spend more to fix software that they have already purchased. You could fix the code in your program and then fix all of the deployed versions, but then there are another 111 versions of the code in use. You'd need to fix all of them individually, and that's a lot of effort. What would be great is if you could fix it once on your machine, and then distribute the fixed version to everyone else. That's what the MVC architecture helps you do. The key idea is that the model makes no assumptions about presentation. All it does is store data and provide access to that data via a set of predefined methods. The view/controller is then responsible for taking that data in its raw form and converting it into a format suitable for its own particular needs. Consider for example a simple example of the code for generating a random quote within an applet:
The implementation here is quite simple, but if we wanted to move it to another applet we'd still need to copy and paste the ArrayList setup, the setupQuotes method and the random generation of a quote. We'd need to find some mechanism for triggering the quote being generated, and we'd need to find a replacement for the displayQuote JLabel. The more complicated the implementation gets, the more code needs to be copied across, and the more patchwork stitching needs to be applied to get it working properly. This is a problem. It would be much better if we simply provided a seperate class and then let any view/controller make use of that:
Each of the view/controllers will create an instance of this class, and call the appropriate methods at the appropriate juncture. Our generator makes no assumptions about presentation - it doesn't care if the quote is going to go into a JLabel, or displayed directly to the console. It puts the emphasis for the functionality onto one class (the model), and the emphasis for interacting with the user and displaying the results onto another class (the view/controller). In this way, a good separation of roles is achieved. For example, view one (an applet):
And view two (a console application):
This is a complicated idea... we'll examine it more thoroughly throughout the course of this book as we look at various case studies that adopt the MVC architecture throughout.
We're going to look at the idea of objects and classes a little more closely by developing a class for our own personal use. None of the functionality for this class is provided for us by Java - we must build it ourselves making use of the various concepts we have covered this far in the book. Let's cast ourselves into the role of software developers. We have our programming language (Java), we have our carbonated beverages, we have our junk food by the keyboard - we're ready to begin developing a solution to a real world problem. Or at least, we're ready to develop the solution to a simplified real world problem. Consider the following scenario: The 'We Love Money' bank (the bank that likes to say 'no') is developing a Java based applet for accessing customer account information. This applet will be used by tellers of the bank for snooping on their customers and making fun of how little money they make. The applet should allow for a number of different accounts to be stored, each containing the following information: The customer name, the customer address, the customer's balance, any notes that have been made about the customer, the overdraft limit for the customer In addition, the applet must provide the functionality for: Allowing any piece of information stored about a customer to be altered, and Viewing the customer information This is quite a complex program, but there's nothing to stop us developing it entirely as a stand-alone applet without making use of our own class. So why should we worry ourselves with the additional complexity of object orientation? Well, consider all the information that is being stored about a particular customer. There's quite a lot of it, and it's all contextually related. One way to implement this kind of data structure is as a collection of arrays or ArrayLists that are indexed by a common number:
If we assume that the customer indicated by index 1 in myNames is the same customer as indicated by index 1 in myBalances and the same customer as indicated by index 1 in myNotes, then we can use these three ArrayLists to hold a wide range of details about a customer:
This will work - but it is not a particularly elegant solution. What happens if you want to add in another element of data? Say we also wanted to include a description of the customer's account type. We'd need to create a new ArrayList, and populate it with information relevant to already existing customers, and amend any method that accesses the customer data. What if we wanted to change the type of variable used to store a piece of information? Say we wanted to store the balance as a floating point number instead of as an integer? There are many changes that we might like to make as we go along, but the data structure above makes it difficult to actually do so. We also lose the benefit of reuse, which is one of the main selling points of object orientation, but we'll look at that idea in a little more depth later in this chapter. Instead of a data structure such as above, we can use objects to model the information we need to store. We should start small, so as not to be overwhelmed. Rather than worry about storing a number of accounts, we'll first worry about storing a single account. We have a list of the information we need to store about a particular customer, so step one is to develop a class that can hold all of this data:
Then, we need to populate our class with accessor methods - these are the get and set methods for each attribute. We'll also add two constructors - an empty one and one that lets us configure all of the attributes at instantiation.
We can represent this class succinctly using the new UML notation we learned earlier:
Believe it or not, that's most of our work done! We've implemented the bulk of the model of our applet. There are still a few things we need to complete, but we'll come back to those as we expand on the work we have done so far. We now need to develop the view and controller aspects of our program, and we'll do this through the traditional medium of an applet. By now, the process of setting up an applet and drawing controls on the screen should be familiar, so we won't go over them in any depth. We also won't worry too much about the user interface we set up - this is not an applet that is genuinely going to be used by anyone, and so we can ignore the finer points of GUI design in favour of explaining the concepts we are most interested in. For this interface, we'll need some GUI components. We'll concentrate first on displaying information:
We know how to do all of this (from chapters four and five), so we can add them to our applet using the following names:
Now, we have our view, and we have our model. All we have to do is link them up. First of all, we need to create an instance of our Account object:
Then we need to pull the information for the labels from this object - we can do this using the accessor methods we created. We can do this as soon as we create the labels, but since this is something that we may wish to do a number of times, it makes sense to put the code into a separate method. We'll call this method updateLabels:
Now, all we need to do is call updateLabels at the end of our init method and our labels will display the information from the model. Neat! We can use this same idea to provide the functionality for setting the data stored in our object - in other word, the controller aspect. For this, we'll need to add a JTextField (called myInfo), and buttons for each action. We'll call these buttons: setName, setAddress, setBalance, setOverdraft and setNote. When a button is pressed, we'll pull out the information that is contained within the text component and store it in a temporary variable (temp), and then we'll check to see what action should be performed (indicated by the name of the button). We'll then call the appropriate accessor method on our account object, passing in a suitable piece of data. In the case of name, address and note we can just pass in the variable temp. For balance and overdraft, we'll have to parse the temp variable into a double first. Once we've called the appropriate accessor method on our object, we'll then trigger the updateLabels method to have our changes reflected in the applet:
Now we have the model, the view, and the controller implemented. There's only once change we have left to make, and that's to allow us to store more than one account in our applet. We already know how to do this - we can use an ArrayList to store our own objects in exactly the same way we've used them to store other pieces of data. However, we do need to add a little bit more functionality to our applet to make it work properly. We need to add some way of moving through an ArrayList of accounts - we'll do this through a next and previous pair of buttons. First we need our ArrayList:
We will use our myAccount object as before, but it will hold the details about the current account - this means we don't have to change much of our code to add in multiple accounts. We'll also need an integer index that indicates where we currently are in the ArrayList. We're not going to worry about actually allowing the user to add new elements to the ArrayList in this example - implementing this functionality is left as an exercise for the interested reader. We will populate the database entirely in our applet in a method called populateDatabase:
Now we need to add the code for our next and previous buttons. When these buttons are pressed, we need to alter the index variable accordingly and check to make sure that we're not going over the bounds of our array... if we are, we'll display a message box to that effect. If we're not going outside the bounds of our accountDatabase, then we set our myAccount object to be the object currently pointed to by index in the accountDatabase. So, for our next button:
And for our previous button:
There's only one small problem now - when we move to a new record after changing our current record, the changes don't save. We need to update the state of our record before we move on to the next - we'll do this by using the set method of our ArrayList before we change the value of index:
And that's it - a solution implemented using objects, and the model-view-controller architecture. The additional complexity in implementing this program without using objects is considerable, and the interested student is invited to make an attempt at implementing the requirements without the use of objects - such an attempt is likely to be very instructional!
Consider for example the complexity of maintaining a separate ArrayList for each piece of information. In itself, this is not hugely problematic, although it is inconvenient. The real problem comes when you start expanding the application so that it deals with multiple accounts - very soon you start getting into the problem of ArrayLists that contain ArrayLists, and the complexity of dealing with this rather convoluted data structure greatly hampers effective software development.
In this chapter we have touched upon the idea of objects and classes - of a necessity this has been a somewhat superficial look at the concept, since the real details of object orientation are very complex and require an understanding of the fundamentals of syntax and program design that we simply have not yet covered in the book. We will be returning to the idea of object orientation in program design repeatedly during future chapters. Further ReadingThe following table details further reading on the topic in this chapter, and also any external resources that you may find useful.
|
| Previous | Table of Contents | Next |
© 2004-2006 Michael James Heron