This series, The Object-Oriented Thought Process, is intended for someone just learning an object-oriented language and wants to understand the basic concepts before jumping into the code or someone who wants to understand the infrastructure behind an OOP language they are already using. Click here to start at the beginning of the series.
In keeping with the code examples used in the previous articles, Java will be the language used to implement the concepts in code. One of the reasons that I like to use Java is because you can download the Java compiler for personal use at the Sun Microsystems Web site http://java.sun.com/. You can download the J2SE 1.4.2 SDK (software development kit) to compile and execute these applications and I will provide the code listings for all examples in this article. I have the SDK 1.4.0 loaded on my machine. I will also provide figures and the output (when appropriate) for these examples. See the previous articles in this series for detailed descriptions for compiling and running all the code examples.
In the previous article in this series, you investigated a potential conflict between two of the most fundamental object-oriented concepts—inheritance and encapsulation. You mitigated this conflict by packaging your classes. By organizing your classes in packages, you can create much more secure designs for your object models and thus your applications.
By creating these packages, you provide the users with very specific guidelines so that they can utilize your programming interfaces. In fact, unless you are very clear as to what these interfaces are, there will be no way for anyone to effectively employ the classes that you meticulously create. Thus, it is vitally important to put a significant amount of thought into how to design these interfaces—which are defined by what are called object signatures.
What’s in a Signature?
Although many people might not even be able to define the term, object signatures may well be one of the most important object-oriented concepts that you will learn. Whenever you design, implement, use, test, and so forth, you must pay attention to the signatures of the object involved. Actually, when you speak of object signatures, you are really talking about the signatures of the object’s methods that are required to use the object.
It is helpful to think of the signatures of an object as the interfaces to that object. This means that you would use these signatures to invoke object services. Signatures are often known by other names. That is because signatures are nothing more than how you call (invoke) a method. In a structured environment, the signature can be thought of in a similar manner—how you invoke a procedure, routine, or function. Look at an example familiar to most structured programmers—using math functions.
In a structured environment, the math functions are most likely kept in a corresponding math library. In an object-oriented environment, the concept is similar. Specifically, Java uses what are called packages. Thus, the math functionality is provided by methods in the java.lang.Math package. The methods correspond to the services provided by the language via its objects. Now, focus on one of the methods in the java.lang.Math package, the sqrt() method. More to the point; first consider how a method to calculate a square root is used and how it is implemented.
Interface/Implementation
Several times in previous articles in this series, you touched upon the issue of interfaces and implementations. In this section, you see how these concepts are put into practice.
Consider the following problem:
You want to provide a programming interface that, when invoked, will return the square root of a number.
Most people, when asked, are not able to provide the formula for calculating a square root. Although they may understand what a square root is, they may not have the foggiest idea of how it is derived. Fortunately for you, due to the forethought of the Java compiler and language designers, developers do not have to understand these details; they only need to know how to use the service provided by the square root method provided by the language.
For example, as I indicated earlier, in the java.lang.Math package there is a method called sqrt(). This method provides the service of calculating the square root of a number sent via the parameter line and returning the result. The question is how to take advantage of this service so that you can use it appropriately. The answer to this question is via the signature of the sqrt() method. This is why signatures are so important; they provide the mechanism to utilize an object service.
How do you find the proper service? All services are found in the Application Programmers Interface of the language (the API specifications). To find the Java API specifications, in this case for v1.4.2, you can visit the site: http://java.sun.com/j2se/1.4.2/docs/api/index.html. Figure 1 shows what this page looks like.
Figure 1
This Web page contains all of the APIs available when developing a Java 2, standard edition application. In short, this Web page contains all of the method signatures (services) available when developing a Java 2, standard edition application. How do you find the one that you are interested in—a service that provides the result for a square root calculation? Click on the Index link at the top of the page—see Figure 2.
Figure 2
Then, click on the S in the alphabet provided (because it is likely that a method to calculate the square root will start with sq. When you scroll through these methods, you come across one that is indeed called sqrt()—see Figure 3.
Figure 3
Here we have found the signature of the method that we are interested in:
sqrt(double) – Static method in class java.lang.Math
Returns the correctly rounded positive square root of a double value.
To utilize the sqrt() service in an application, you need focus only on the signature. As an exercise, define a class called Calculate that will perform various mathematical operations. One of these operations is to calculate a square root. Listing 1 shows what the design of this class might look like.
Listing 1: The Calculate Class
// Class Calculate
public class Calculate {
private double result;
public double squareRoot (double value){
return result;
}
}
In this class, you have provided the method signature for your squareRoot() service.
public double squareRoot (double value)
This method’s name is squareRoot(); a single double parameter is passed, and it returns a double value.
Recall from earlier columns in this series that a signature is basically three things:
- Method Name
- Parameter List
- Return Value
There is a point of contention that should be mentioned at this point. Although you must carefully consider the return type in method signatures when designing, the Java compiler does not check the return type as part of the overload process (some languages do include the return type). This may seem somewhat confusing; look at a simple example using your squareRoot() method.
Theoretically, if the return type were indeed included in a Java signature, you could have the following methods in the same class:
public double squareRoot (double value)
public int squareRoot (double value)
These methods return different types. Even though the rest of the signature is identical, the Java language design does not allow overloaded methods to have different return types. The Java compiler will catch this and report an error. Even though the return types are different, the Java compiler will not allow methods with the same name and parameter list as long as there is a different return type. Thus, the signature for a Java method is effectively just the method name and its parameter list. That said, each signature must be unique—that is what allows you to overload methods as in the example above.
The method signature that you will use for your example is:
public double squareRoot (double value)
Listing 2 shows what the code would look like if you used the java Math package to do the dirty work.
Listing 2: The Calculate Class
// Class Calculate
public class Calculate {
private double result;
public double squareRoot(double value){
result = java.lang.Math.sqrt(value);
return result;
}
}
Listing 3 shows the application that actually uses the squareRoot class.
Listing 3: The Application
class TestSQRT {
public static void main(String args[]) {
Calculate calc = new Calculate();
double result = calc.squareRoot(16);
System.out.println(“result = ” + result);
}
}
When this application is executed, the results shown in Figure 4 are displayed. The calculation of the square root of 16 is obviously 4.
Figure 4
What you have done here is provide an object service that calculates the square root of a number. The important point here is the way the application uses the service: invoking a method by using its signature appropriately.
When developers create APIs, they are in effect creating a collection of signatures for programmers to utilize. Although the APIs are accessible to the programmers (these are the public interfaces), the code behind the so-called curtain is not accessible—it is not even viewable. So, when the programmer sees this API:
public double squareRoot(double value);
The programmer does not see this (the implementation):
result = java.lang.Math.sqrt(value);
The fact that the code is hidden is a major plus. There is a very real advantage in separating the interface from the implementation. Among other issues, if the programmer knew what the code looked like, it might actually influence some design decisions. Your goal is to make the interface as independent as possible from the implementation. Just consider what the programmer really needs to know when using the sqrt() method.
- Method Name
- Parameter List
- Return Value
This should look very familiar—it is the signature. All the programmer wants to get from the sqrt() method is the correct answer; he/she does not really care how the answer is derived. Most likely, the majority of people would not even know how to calculate a square root by hand. Thus, you could change the implementation of our sqrt() method, and it WILL NOT require a change in the application. For example, you can create a square root with the following formula.
sqrt =exp( log(x)/2)
This formula can then be substituted for the original code in the Calculate class as seen in Listing 4.
Listing 4: The New Calculate Class
// Class Calculate
public class Calculate {
private double result;
public double squareRoot(double value){
result = java.lang.Math.exp(java.lang.Math.log(value)/2);
return result;
}
}
Focus on the fact that the code in Listing 2 and Listing 4 returns the exact same result—despite the fact that the implementation is different. Yet, the interface is the same, and the result is the same. In both cases, you get the result of the method with this line of code.
double result = calc.squareRoot(16);
And, although you might not want to calculate the square root using the code in Listing 4, the fact that you can plug in a new (perhaps better) implementation without forcing a change in the user applications is a major benefit.
Recall the example of the relationship between the interface and the implementation that was presented in an earlier column. Figure 5 illustrates the scenario where you plug in an appliance to take advantage of the electricity coming out of the electric outlet. In Figure 5, the electricity is being produced by a conventional coal power plant. The coal power plant is the implementation. The electric outlet is the interface.
You, as a user of the electricity, do not care where the electricity is coming from—for all you know the electric company could have purchased the power from another state. In fact, a nuclear power plant could be generating the electricity. Or, perhaps your power went out and a generator switched on. In any event, all you need to know to utilize the electricity is to have the proper interface.
Figure 5
Consider an application that connects to a database. If the interfaces in the application are dependent on the code in the implementation, any change to the code inside the method would perhaps force a change in the actual application. However, if you spend the time to design your system properly and separate the implementation from the interfaces, a change to the code should not effect the application, as the diagram shows in Figure 6.
Figure 6
Conclusion
In the database example above, if you are using an SQL Server database and put code in your application specific to SQL Server, then a change to, let’s say, an Oracle database would require a change in the application code. The rule of thumb is to avoid putting code in a frontline application that is specific to any specific implementation.
At first, this may seem counterintuitive—how do you not put code for a SQL Server application in the actual application? The answer is to utilize the concept of wrappers. You can “wrap” any implementation specific code in an intermediate class that acts as a “buffer” of sorts. The application would then call methods (services) or the intermediate class, a kind-of middleware. In the next column, you will explore the concept of wrappers and how they improve your designs and how you can take advantage of wrappers to upgrade some legacy code—even if the code is not object-oriented.
References
Gilbert, Stephen, and Bill McCarty: Object-Oriented Design in Java. The Waite Group, 1998.
Meyers, Scott: Effective C++. Addison-Wesley, 1992.
Tyma, Paul, Gabriel Torok and Troy Downing: Java Primer Plus. The Waite Group, 1996.
Ambler, Scott: The Object Primer. Cambridge University Press, 1998.
Jaworski, Jamie: Java 1.1 Developers Guide. Sams Publishing, 1997.
About the Author
Matt Weisfeld is a faculty member at Cuyahoga Community College (Tri-C) in Cleveland, Ohio. Matt is a member of the Information Technology department, teaching programming languages such as C++, Java, and C# .NET as well as various web technologies. Prior to joining Tri-C, Matt spent 20 years in the information technology industry gaining experience in software development, project management, business development, corporate training, and part-time teaching. Matt holds an MS in computer science and an MBA in project management. Besides the first edition of The Object-Oriented Thought Process, Matt has published two other computer books, and more than a dozen articles in magazines and journals such as Dr. Dobb’s Journal, The C/C++ Users Journal, Software Development Magazine, Java Report, and the international journal Project Management. Matt has presented at conferences throughout the United States and Canada.
The material presented in these articles is based on material from the second edition of his book, The Object-Oriented Thought Process, 2nd edition. The Object-Oriented Thought Process is intended for anyone who needs to understand the basic object-oriented concepts before jumping into the code.