This is the thirty-sixth installment in a series of articles about fundamental object-oriented (O-O) concepts and related object-oriented technologies. The material presented in these articles is based on material from the second edition of my book, The Object-Oriented Thought Process, 2nd edition. The Object-Oriented Thought Process is intended for anyone who needs to understand basic object-oriented concepts and technologies before jumping directly into the code. Click here to start at the beginning of this entire series.
If you would like to start at part 1 of this topic, Object Integrity & Security: Duplicating Objects, click here.
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 standard edition, J2SE 5.0, at http://java.sun.com/j2se/1.5.0/download.jsp to compile and execute these applications. I often reference the Java J2SE 5.0 API documentation and I recommend that you explore the Java API further. Code listings are provided for all examples in this article as well as figures and output (when appropriate). See the first article in this series for detailed descriptions for compiling and running all the code examples (http://www.developer.com/design/article.php/3304881).
The code examples in this series are meant to be a hands-on experience. There are many code listings and figures of the output produced from these code examples. Please boot up your computer and run these exercises as you read through the text.
For the past several months, you have been exploring various issues regarding object integrity, security, and performance. In this month’s article, you continue with this theme and begin to learn how objects are duplicated. This is not a trivial issue. Although copying primitives such as numbers is mostly straightforward, copying objects is a more complicated task. Completely duplicating an object is a multi-step process.
It is important to realize that duplicating objects can be a complicated process, and that not fully understanding the process can lead to problems. These problems are akin to the difference between a syntax error and a logic error. You can have code that is compiling and executing, yet the results can be totally incorrect and possibly dangerous.
Copying Objects
In the two previous articles, you covered the topic of cloning objects. One of the primary points discussed was the fact that duplicating objects is not exactly straightforward.
The phrase ‘not exactly straightforward’ might not be strong enough. The term ‘confusing’ might be more appropriate. As you discussed, the problem concerns the references and how they are handled.
Continue with the example you used previously. There were two classes, Duplicate.java and Dog.java, as seen in Listing 1 and Listing 2.
Listing 1: The Previous Example (Dog.java)
// Class Dog class Dog implements Cloneable { String name; String breed; Dog ref; public Dog(String n, String b) { name = n; breed = b; } public Dog copy(String n, String b) { ref = (Dog)clone(); ref.setName(n); ref.setBreed(b); return ref; } public Object clone() { try { System.out.println("Inside clone"); return super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.toString()); } } public String getName() { return name; } public void setName(String n) { name = n; } public String getBreed() { return breed; } public void setBreed(String b) { breed = b; } }
These examples are purposely kept as small as possible. Thus, you have only two attributes for the Dog class and their corresponding access modifiers.
In the Duplicate class, you create a single new object called fido and then copy the fido object and then rename it spot.
There is one issue that requires special attention. This article is meant to explore the issue of cloning/copying objects. To accomplish this, a simple example is presented. However, there are always alternate means to accomplish the same task.
Consider the fact that if you want to create a second dog object called spot, you could simply create an entirely new object called spot, just as you did for the object fido.
Dog spot = new Dog("spot", "mutt");
This works fine. In fact, it may very well work better in a simple case. However, assume that you have an entire litter of dogs, and the Dog class identifies 25 different attributes. If each puppy in the litter shares 20 of the attributes, with only 5 that vary, it may make sense to completely clone the objects from a standard object and then make the few changes for each. This saves you the hassle of initializing each of the 20 attributes that they share.
As in many programming scenarios, you can take this even further and see what happens if you want to copy 1,000 objects that share most of their attributes, or 1,000,000? In short, it all depends on the application.
Listing 2: The Previous Example (Duplicate.java)
// Class Duplicate public class Duplicate { public static void main(String[] args) { Dog fido = new Dog("fido", "retriever"); Dog spot; spot = (Dog)fido.copy("spot", "mutt"); System.out.print("fido : = "); System.out.print("name = " + fido.getName()); System.out.println(", breed = " + fido.getBreed()); System.out.print("spot : = "); System.out.print("name = " + spot.getName()); System.out.println(", breed = " + spot.getBreed()); } }
This code has evolved through several iterations to the point where an object can be cloned, and then its attributes can be initialized to unique values (please reference the previous two articles in this series).
Diagram 1 illustrates what is actually happening here. The original object, fido, is cloned. Cloning fido creates the second object, spot; however, cloning implies that both objects are identical and you do not want the second object to have the same exact attributes as the first. Thus, you provide a copy() method in the class that allows you to create a totally new object, but with unique attributes. In Diagram 1, you see that there are two totally separate objects and each has its own name.
Diagram 1: An Object with two References and two Objects (different content)
You can prove that this is working the way You intend simply by executing the code. The results are found in Figure 1.
Figure 1: Using the copy() method
A Single Head
For this iteration, you will add another class called Head.
Listing 3: Adding the Headclass
// Class Head class Head { String nose; public Head(String n) { nose = n; } public String getNose() { return nose; } public void setNose(String n) { nose = n; } }
Diagram 2: UML Diagram
You use composition to include the Head class as part of a Dog, as you can see in UML Class shown in Diagram 2. Again, you are using this as a simple example to illustrate the process of cloning/copying objects. An argument can be made that you should not be using a term like nose to represent an attribute. If nose is actually considered a separate object, perhaps you should make it an object unto itself. I just want to point out that, in some designs, you may want to name the attribute something like noseColor.
The deciding factor here would be whether or not you want to reuse a nose object and whether or not nose is made up of other attributes. Even though it might not sound like it, this is an interesting discussion unto itself, and I will leave this discussion for another day.
Listing 4: The Dog class with the Head class
// Class Dog class Dog implements Cloneable { String name; String breed; Dog ref; Head head; public Dog(String n, String b, String s) { name = n; breed = b; head = new Head(s); } public Dog copy(String n, String b, String s) { ref = (Dog)clone(); ref.setName(n); ref.setBreed(b); ref.head.setNose(s); return ref; } public Object clone() { try { System.out.println("Inside clone"); return super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.toString()); } } public String getName() { return name; } public void setName(String n) { name = n; } public String getBreed() { return breed; } public void setBreed(String b) { breed = b; } }
When you run this application using the code in Listing 5, you get a curious result.
When you run this application using the code in Listing 5, you get a curious result.
Listing 5: The application class with the Head class
// Class Duplicate public class Duplicate { public static void main(String[] args) { Dog fido = new Dog("fido", "retriever", "black"); System.out.print("fido : = "); System.out.print("name = " + fido.getName()); System.out.print(", breed = " + fido.getBreed()); System.out.println(", nose = " + fido.head.getNose()); Dog spot; spot = (Dog)fido.copy("spot", "mutt", "pink"); System.out.print("fido : = "); System.out.print("name = " + fido.getName()); System.out.print(", breed = " + fido.getBreed()); System.out.println(", nose = " + fido.head.getNose()); System.out.print("spot : = "); System.out.print("name = " + spot.getName()); System.out.print(", breed = " + spot.getBreed()); System.out.println(", nose = " + spot.head.getNose()); } }
Note that you have added the following line of code to the application in an attempt to give Fido a black nose and Spot a pink nose.
Dog fido = new Dog("fido", "retriever", "black"); .... spot = (Dog)fido.copy("spot", "mutt", "pink");
However, when you run this, you get the results seen in Figure 2.
Figure 2. Providing some color
Notice how fido originally has the correct node color, black. However, after you cloned the object and then set Spot’s nose color to ‘pink’, fido‘s object is now ‘pink’ as well, incorrectly so.
This perfectly illustrates the problem of duplicating objects. It appears that you are, again, duplicating just the reference to the Head object and not actually creating a new one—as intended. As seen in Diagram 3, you may have two totally separate objects, fido and spot; however, they seem to reference the same head object. This is a situation that may cause major problems unless proper testing is performed.
Diagram 3: A Single Head Object
Multiple Heads
To take care of this problem, you must physically create a second head object and assign it to the new Dog object, spot. As usual, there are several ways that you could do this. In this case, you will explicitly create a new head when you copy the Dog object, inside the actual copy( ) method.
ref = (Dog)clone();
Head h1 = new Head(no);
ref.head = h1;
This is a 3-step process. First, the Dog object is cloned. Second, the new Head object is created. Third, you assign the newly created head object to the reference of the cloned Dog object. The Head class is presented in Listing 6 and the completely redesigned Dog class is seen in Listing 7. The Duplicate class is contained in Listing 8.
Listing 6: The new Head class
// Class Head class Head { String nose; public Head(String no) { nose = no; } public String getNose() { return nose; } public void setNose(String no) { nose = no; } }
Listing 7: The new Dog class
// Class Dog class Dog implements Cloneable { String name; String breed; Dog ref; Head head; public Dog(String n, String b, String s) { name = n; breed = b; head = new Head(s); } public Dog copy(String n, String b, String no) { ref = (Dog)clone(); Head h1 = new Head(no); ref.setName(n); ref.setBreed(b); ref.head = h1; return ref; } public Object clone() { try { System.out.println("Inside Dog Clone"); return super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.toString()); } } public String getName() { return name; } public void setName(String n) { name = n; } public String getBreed() { return breed; } public void setBreed(String b) { breed = b; } }
Listing 8: The new Duplicate class
// Class Duplicate public class Duplicate { public static void main(String[] args) { Dog fido = new Dog("fido", "retriever", "black"); System.out.print("fido : = "); System.out.print("name = " + fido.getName()); System.out.print(", breed = " + fido.getBreed()); System.out.println(", nose = " + fido.head.getNose()); Dog spot; spot = (Dog)fido.copy("spot", "mutt", "pink"); System.out.print("fido : = "); System.out.print("name = " + fido.getName()); System.out.print(", breed = " + fido.getBreed()); System.out.println(", nose = " + fido.head.getNose()); System.out.print("spot : = "); System.out.print("name = " + spot.getName()); System.out.print(", breed = " + spot.getBreed()); System.out.println(", nose = " + spot.head.getNose()); } }
When the redesigned application is executed, you can see by the results in Figure 3 that you now retain the proper value of the nose attributes for the fido object. The spot object also has the proper value for its nose attribute.
Figure 3: Separate Head objects
Finally, Diagram 3 provides a graphical representation of what the final version of the code looks like in memory. You have two Dog objects, fido and spot. And each of these objects has its own, separate Head object; fido has a black nose and spot has a pink nose.
Diagram 3: Separate Head Objects
Conclusion
Attempting to keep track of complicated object relationships is a daunting task. Consider the responsibility of modeling complex objects. Think about one of the more common objects, an automobile. This provides a real-world example of how object relationships are modeled. Each car contains so many separate parts that it is hard to even list them. On top of that, take into account how many different parts are manufactured at different sites.
The obvious example is that of an engine. Each car (an object) contains an engine (another object); however, the engine is made up of other parts, like pistons (an object) and camshafts (another object). Pistons are made up of even more objects. And I could go on and on.
In a world of hardware, there are physical objects to help with the manufacturing process; for example, you can hold a piston in your hands. Yet, when modeling an engine in software, you have to be aware of all these relationships and how you will duplicate them. It is important to pay attention to this process; otherwise, it can lead to major confusion.
References
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, C#, and .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 Object-Oriented Thought Process, which is now in its second edition, 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.