CSIS 3701: Advanced Object-Oriented Programming Introduction to Object-Oriented Programming Important Principles for Software Development As you well know by now, writing programs is never easy, even for the short programs that you have written so far. Of course, writing the kind of large applications that people use in the real world -- from tools like word processors to critical applications like the Space Shuttle software -- is far more complex still. The difficulty associated with creating software (defined in terms of time to write and debug, number of errors, etc.) increases at a rate much larger than a linear function -- that is, a program 2n lines long is much more than twice as hard to create as one n lines long. This is due to the possibility that any new code you write may potentially affect any other code. This does not even factor in problems such as: · Most software is not written by a single person in a single period of time -- instead, it is usually written by large groups. It is then often modified over a period of years, usually by still more people. · Expectations of users are growing faster and faster all of the time. This is fed, to some extent, by the rapid advancements in hardware development, which makes it possible to execute more code in the same amount of time. The result of this is larger amount of code being released with less development time. · Computers are a part of more and more functions critical to society, which means that there is increasingly less tolerance for error. The result of this is often called the software crisis; software developers scrambling to keep up with the demands and needs of users. This is not likely to go away any time soon -- after all the phrase "software crisis" itself was coined over 25 years ago! There are two main (related) principles that can help make software development "in the large" easier: Abstraction This refers to the ability to use software (that is, to be able to incorporate it into your own software) without having to understand its implementation -- that is, you only have to know what it does, not how it does it. It is much easier for a group of people to write a large program if they only have to worry about how their own code works, and how to use the code written by others, instead of understanding all details of the program as a whole. Reuse Obviously, the more existing code that you can reuse, the less code that you will have to write (and debug!). This requires, however, that your existing code must be flexible enough to be adapted to this new situation without having to be rewritten from scratch -- something very difficult to achieve in practice. You have already seen these principles at work with functions -- functions are abstractions, in the sense that you can call them without knowing any more than the types they take as parameters and return, and they may be reused multiple times with different parameter values. As you will see, these two principles are an important part of object-oriented programming as well: · Abstraction is facilitated through the inclusion of data and functions into a single object, and features such as encapsulation and exception handling encourage maximum independence between separate modules. · Reuse is facilitated through features such as inheritance, and polymorphism and interface classes allow code to be extremely general and flexible. Terminology note: Throughout most of this reference, we will use the term "user" to refer to other developers rather than end users. That is, the "developer" implements some code; the "user" incorporates it into their own code, usually by calling it in some way. The Eckel textbook also refers to other programmers who use your code as "client programmers" Introduction to Object-Oriented Programming Object-oriented programming (often amusingly abbreviated "OOP") is fundamentally different from the procedural programming you have done up to now (in C, for example). The fundamental premise of procedural programming may be summarized by the following statement: In procedural programming, data and functions are two different things. This may seem like an obvious thing to say, but the vast majority of things in the world cannot be described this way. For example, consider describing a "light switch" (like the one in the room you are in) in the above terms: · A light switch contains data, in the sense that it is either on or off, so we cannot consider it to merely be a function. · A light switch acts like a function, in the sense that it is capable of accepting "input" (in this case, flipping the switch) which "executes code" (switching the light on/off), so we cannot consider it to merely be data. This light switch is both data and a function. If you think about it, most things that we interact with in the real world are also -- for example, a car is both data (make, model, fuel level, current speed, etc.) and functions (what happens when the gas, brake, steering wheel, etc. are used). These are the kind of things that we call objects: An object contains both data (a state) and functions (the methods used to change its state). Terminology note: We will use the words "state" and "methods to change that state" instead of "data" and "functions", as those seem more natural for objects. It has taken computer programming a bit of time to catch up with this "object model" -- after all, the first large programs were written to perform complex numeric computations, in which data (such as fuel) and functions (such as sqrt(float)) best fit the procedural model. In addition, the procedural model is a close match for the internal architecture of the computer, in which data and programs are stored and handled in very different ways. Now, however, numeric computation is a minor (and relatively simple) application of computing. Most major applications of computing may be thought of as "objects" of some sort. For example, consider a "word processor" -- it contains state data (the document, margin settings, etc.) and methods for changing that state (typing in text, using the menus, etc.). While it is possible to use procedural programming to create such programs, of course, object-oriented programming is often more effective. Methods as an Abstract Interface As a more programming-related example, consider a database. A database obviously contains state data (the records that it stores) that represents its current state. It also contains methods for viewing and manipulating that data -- code for searching the records, for sorting them, and for adding, modifying, and deleting records. People who use the "database object" interact with it through these methods: USER -----> search ---->sort -------->add ---------> database records This makes the database object an abstraction, in the following ways: 1. Users do not need to understand how the methods for searching, sorting, adding, etc. are implemented in order to use them -- they can just invoke them, as they would any other abstract function. 2. Users do not need to understand how the data is represented (as a linked list, array, etc.) in order to use the database -- they can manipulate it indirectly through the given methods. In a sense, this means that these methods act as an interface to the data of the database. That is, the user will not directly manipulate the records, but will instead manipulate them through these methods. Constructing Objects An important part of this abstraction is defining what happens when an object is created. If the user should not have to understand the internal representation, then they should not have to do anything special to set up the initial state of the object (such as setting up an "empty" database when one is created). This should be done automatically when an object is created. Most object-oriented languages allow you to define code called a constructor for a class. This code is automatically run whenever an object in the class is created, and can be used to set the initial state of the object. Defining Classes of Objects An important distinction must be made between objects and classes of objects: · A class defines the properties (that is, the state variables and methods) of a particular type of object. It represents a general concept (such as a "database"). · An object is a member of a class, and has specific values for its state variables. It represents a specific instance of that class (such as a database used as a gradebook, containing a specific list of students and grades). As an object-oriented programmer, the main thing that you will do is to create classes, defining the internal representation (the state variables) and the methods to manipulate the internal representation. Other programmers who use your classes will create individual objects in those classes, and use them by invoking their methods. For example, you would create a database class by defining: · The internal representation of the database (as parallel arrays, a linked list, etc.) · What happens when a database is constructed - that is, the initial state of the internal representation of that database obejct. · The methods of the database class - what methods the user may invoke upon a database, and how they affect the internal representation of that database. The user would then create individual instances of database objects (invoking the constructor) and would manipulate them (using their methods) without having to understand anything about how those methods or the internal representation work. One way to think of this is that the programmer defines a new "type", and users create and manipulate "variables" of that new type. Copyright © 2000 by Dr. John R. Sullins CSIS 3701: Advanced Object-Oriented Programming Object-Oriented Syntax in Java In this section, we introduce the basic syntax of creating and using classes in Java. Further sections will expand upon this, giving more advanced syntax and more details on the concepts behind them. An Example Sum Class For this section, we will create a very simple class called Sum that represents the concept of a "running total". More specifically: · Objects in this class will keep a running total of the integers that are passed to them as a state variable. · When an object in this class is constructed its running total will initially be 0. · Users may use methods to add new integers to the total (using a method called add) and find the current running total (using a method called getSum). This could be implemented with the flowing class, which would be stored in a file called Sum.java. // Sum is a class representing a running integer total. // Users can add to the total, and find the total.public class Sum { private int total; // Internal state variable // The constructor sets the internal running total to 0. public Sum() { total = 0; } // The add method increments the running total, modifying // the state of the object. public void add(int x) { total = total + x; } // The getSum method returns the current value of the // running total. It does not modify the state. public int getSum() { return total; } } Some syntax nodes about the above example: Basic class structure: · The keyword class is used to indicate that Sum is a new class with the constructor and methods defined inside the { }. Unlike C++, you cannot define the methods separately from the class. · Defining the variable int total inside the class makes it a state variable of the class. Every Sum object will have its own individual total. · Using the word public before the class, constructor, and methods make them available to the user. Using the word private before the state variable hides it from the user. This is related to a topic called encapsulation, which we will talk about in more detail later. · The class must be stored in a .java file with the same name as the class - in this case, Sum.java. Constructor: · The constructor for the class has the same name as the class. This is true for most object-oriented programming languages. · The constructor does not return anything (not even void). Forgetting this can lead to some strange compiler errors. · Other than the above two qualifications, there are no restrictions on what a constructor can do. They can take parameters (we will see examples of this later), read from files, allocating memory, etc. - in other words, do whatever is necessary to set up the initial state of the object. Methods: · The syntax for a method is very similar to that of a function in C - you must define parameters and return type, as well as the code that is executed when the method is invoked. · Note that the variable total is used in the methods (and the constructor as well), but is not declared as a local variable of any of these. In this case, total refers to the state variable of the object - invoking any of the methods modifies or accesses the current value of that state variable. This means that if we were to call the add method of some Sum called S, the call would end up changing S's total state variable. In a sense, this means that these methods act as an interface to the state variables of each Sum. That is, the user will not directly manipulate the state variables, but will instead manipulate them through these methods: USER -----> addgetSum ---> total This allows users to treat our Sum objects as abstractions. They only need to know what interface methods affect the Sum, but not how the Sum is represented. Finally, one very important thing to be aware of is that everything in Java must be a member of a class. This accounts for some of the more unusual looking syntax in Java, such as the main function being inside of a class. There are ways around this using the static keyword that we will see later. Constructing Objects As mentioned previously, objects (whether they be part of a Java library like Button or programmer-defined like Sum) must be created using the new command. This does the following things: 1. Allocates space in memory for the object. 2. Invokes the constructor for the object. 3. Returns a reference to the object. For example, to create a new Sum object called odometer we would do the following: Sum odometer = new Sum(); Note that (unlike C++) you must use the parentheses ( ) to invoke the constructor even if there are no parameters. At this point, we can think of odometer as looking something like this: void add(int) int getSum() total: 0 Executing object methods The syntax to invoke the methods of an object is: objectname.methodname(parameters); For example, to add 37 to our Sum odometer, we would do the following: odometer.add(37); As described above, this would call the add method for odometer. This would have the effect of changing the total state variable of odometer. In effect, it would be identical to: odometer.total = odometer.total + 37; At this point, we can think of odometer as looking something like this: void add(int) int getSum() total: 37 And after another add: odometer.add(62); void add(int) int getSum() total: 99 Given this current state of course701, the message: int distanceSoFar = odometer.getSum(); would return 99. Terminology note: We often use the term message instead of "call" to describe the process of invoking a method. This better expresses the idea that we are sending that call to a particular object. For example, we could say that odometer.add(37) is "sending a message to the odometer object to add the parameter 37". Syntax Notes: · Note that these calls are sent to odometer, and no other object (which is another reason that this is referred to as a "message"). For example, if we had constructed another Sum called somethingElse: Sum odometer(), somethingElse(); then a call of the form: somethingElse.add(86); would only affect the total variable of somethingElse. It would have no affect on odometer. · If a state variable (such as total) is redeclared in a method, then references to that variable will affect the local variable only (sort of like what happens if you redeclare a global variable locally in C). For example: public void doNothing() { int total; // total is redeclared locally total = 0; // this only changes the local total } Overloaded Methods and Constructors One potential problem with the convention that the name of the constructor must be the same as the name of the class is that it might only allow us one way to initialize objects in the class - that is, while we may have many different methods in a class (such as add and getSum), we can only have one constructor (Sum), since it must have the same name as the class. Suppose I wanted to give the user two different options for creating Sum objects. Specifically: · A default constructor, which allows the user to create a Sum with an initial total of 0 (like the one given above). · An additional constructor, which allowed the user to specify the initial total to something other than 0. That is, I would like to modify the above class as follows: // Sum is a class representing a running integer total. // Users can add to the total, and find the total.public class Sum { private int total; // Internal state variable // The default constructor sets the internal running total to 0. public Sum() { total = 0; } // The additional constructor sets the running total to the given // parameter. public Sum(int t) { total = t; } // The add method increments the running total. public void add(int x) { total = total + x; } // The getSum method returns the current value of the // running total. public int getSum() { return total; } } In the above example, I have overloaded the Sum constructor. Overloading a method or constructor refers to reusing the same name with more than one definition. In C, this would give us a compiler error, as you cannot redefine a function any more than you can redefine a variable. In Java, however, we can give the same constructor or method more than one definition, as long as it is possible to disambiguate between them based on either: · The number of parameters · The type of parameters For example, consider the following lines of code: Sum s1 = new Sum(); Sum s2 = new Sum(42); Java will choose the definition of Sum that matches the type and number of parameters. s1 would be created using the first constructor, and s2 would be created using the second constructor. An Example Application To better illustrate how objects can be use, we give a sample Java application that creates a Sum object and uses it to compute trip mileage. You won't understand everything this application does at this point; however, you should be able to understand all of the lines in bold, which are related to the Sum. import javax.swing.*;import java.awt.event.*;public class MileageApp extends JFrame implements ActionListener, WindowListener { JButton moreMileage; JTextField getMileage, showMileage; Sum odometer; // a reference to a Sum object public MileageApp() { // Create visual objects moreMileage = new JButton("add distance to next city"); getMileage = new JTextField(5); showMileage = new JTextField(5); showMileage.setEditable(false); JLabel showLabel = new JLabel("Total distance: "); // Create a panel to display the objects, and add them // to the panel. JPanel P = new JPanel(); getContentPane().add(P); P.add(moreMileage); P.add(getMileage); P.add(showLabel); P.add(showMileage); // Listen for events on the button, and for the closing // of the window. moreMileage.addActionListener(this); addWindowListener(this); // create the Sum object odometer = new Sum(); } // This event handler is invoked when the button is // pressed. It gets the number in the getMileage field // and sends it to the odometer. It then gets the total // mileage from the odometer and displays it in the // showMileage field. public void actionPerformed(ActionEvent e) { // read the number of miles from the textfield and // convert it to an integer int miles = Integer.parseInt(getMileage.getText()); // Invoke the add method to add that many miles to the // odometer. odometer.add(miles); // Use the getSum method to get the total number of // miles. int totalMiles = odometer.getSum(); // Display it in the other textfield showMileage.setText("" + totalMiles); } // Called by java, use to create and display a MileageApp object public static void main(String[] args) { MileageApp m = new MileageApp(); // create an object m.setSize(280, 120); // set its size m.show(); // display it } } When run, the applet will look something like this: Another important thing to note about this example is the way the developer of the Sum class and the author of this application are able to know as little as possible about each other. Specifically: · The author of the application of the Sum class is able to use it without understanding how it works - to them, it is just something to keep track of mileage traveled so far. · The author of the Sum class does not have to know how the class will be used - that is, they don't have to be aware in any way that it is being used to keep track of mileage traveled. Arrays and Strings Revisited Now that we have introduced basic OOP syntax and concepts in Java, we can talk a bit more about arrays and Strings in Java. While arrays are not completely treated like objects (there are no methods or constructors), they behave like objects in certain ways. Like other objects, arrays must be allocated with the new command. They also have properties that can be accessed (sort of like state variables in a class). The most useful property to be able to find out is the length of an array. This can be accessed using the form: arrayname.length For example, we can easily iterate through an entire array using a for loop by accessing its length property. For example: for (int i = 0; i < A.length; i++) { … } Strings are full-fledged classes in Java, with many methods for accessing individual characters, finding the length, searching, etc. Probably the most useful method, however, is the equals method, which allows you to compare two strings to determine whether they contain the same sequence of characters (recall that this cannot be done with the == operator, as it just checks whether two things refer to the same object). For example: String name = T.getText(); // read in a name from // textfield T if (name.equals("Fred")) { // not name == "Fred"!!! T.setText("hello Fred!"); } Copyright © 2000 by Dr. John R. Sullins