http://www.developer.com/

Back to article

Object Responsibility


May 24, 2005

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 this series.

In the last column, you explored the history and evolution of object-oriented languages—to provide a basic understanding of how object-oriented languages developed. In earlier columns, you explored a number of issues relating to the development of object-oriented systems and provided a basis for underlying fundamental object-oriented concepts. In this article, you will explore one of the fundamental characteristics that make for good class design. Remember that it is the class that provides the blueprint for all object-oriented design.

An Object Must be Responsible for Itself

One of the most important mantras used in object-oriented design is that of "an object must be responsible for itself". I first heard this phrase while taking my first class in object-oriented programming class in Smalltalk. Because I had never programmed in an object-oriented language before, I was curious as to what this really meant.

While there are many ways to approach this point, it really boils down to the concept of encapsulation, just as everything does when dealing with properly designed classes. The whole concept of an object is that it contains both attributes and behaviors. In structured programming, there is code and there is data, and the two are thought of as separate entities. The power of an object is that these two separate entities are encapsulated together to form a single, atomic entity. It is in this fundamental concept of an encapsulated, atomic entity that you come to realize how and why an object is responsible for itself.

A straightforward definition for object-responsibility is this: An object must contain the data (attributes) and code (methods) necessary to perform any and all services that are required by the object. This means that the object must have the capability to perform required services itself or at least know how to find and invoke these services. Rather than attempt to further refine the definition, take a look at an example that illustrates this responsibility concept.

An Internet Example

Consider a simple banner (a Java object/applet) that is placed at the top of a web page. The banner is a basic rectangle which cycles through multiple text messages. The banner contains several attributes such as the background color of the banner, the color of each of the messages, the size of the rectangle, and so forth. The banner also contains at least one method, the code that controls the fading in and out of the various messages.

This Java object is invoked by HTML code in the Web page, the code might look something like this:

<object code="banner.class" width="600" height="70">

The important thing to note here is that everything in the object is contained neatly in the class file. As stated above, "banner.class" includes all the attributes and all the code that is necessary for the banner to execute properly.

To further understand the power of encapsulation, it is important to realize that "banner.class" is, most likely, physically located somewhere on the Internet—not on the local machine. The implication is that while the browser can invoke the banner object, it has no idea what to do with it—it simply loads it. In short, the browser is not responsible for any functionality of the banner object. The banner object is responsible for itself. Any functionality (services) that the banner must deliver, the banner must perform and manage itself.

One question that may come up is this: if the banner object is totally self contained, how can the Web page communicate with the object? There may be some basic functionality that the banner performs that requires no information; however, consider that different Web pages may want to use the same banner object. It is likely that each of the sites will want to display different messages and set the banner attributes differently. For example, one site may want the banner background to be red, while another may want the banner background to be blue. The answer to this is simple: parameters are used to convey information from a specific Web page to the object invoked by that page. Thus, we can get unique information into a self-contained object.

An Application Example

While the Internet provides a good example as to how self-contained objects are used, we can also consider standalone application when looking for an example to illustrate object responsibility. One of my favorite examples is that of a generic paint program. No doubt that everyone has used a basic drawing program that allows a user to select a shape from a pallet and then drag it to a canvas, where the shape is dropped and displayed. From the user's perspective, this activity is accomplished by a variety of mouse actions. First, the user hovers over a specific shape with the mouse (perhaps a circle), right clicks, drags the shape to a location on the canvas and then lifts his/her finger off the mouse.

This last action causes the shape to be placed at the desired location. In fact, what is really happening when our user's finger is lifted from the mouse button? We can use this action to illustrate our object responsibility concept. Let's assume that when the finger is lifted from the mouse button that the resulting message from this event is simply: draw. Consider the mouse as an object and that the mouse is only responsible for itself as well. The mouse can, and should only do mouse things. In this context, when the finger is lifted from the mouse button, the mouse thing to do is to draw. But draw what? Actually, the mouse doesn't care. It is not the mouse's responsibility to know how to draw anything. Its responsibility is to signal when to draw, not what to draw.

If this is the case (remember that a circle was selected), how does the right thing get drawn? The answer is actually pretty straightforward; the circle knows how to draw itself. The circle is responsible for doing circle things—just like the mouse is responsible for doing mouse things. So, if you select a circle, the circle object that is created knows everything possible about how to use a circle in the application.

This model has a major implication. Because the mouse does not need to know anything about specific shape objects, all it needs to do to draw a shape is to send the message draw. Actually, this will take the form of a method called draw( ). Thus, more shapes can be added without having to change any of the mouse's behavior. The only constraint is that any shape object installed in the application must implement a draw( ) method. If there is no draw( ) method, an exception will be generated when the mouse mouse-up action attempts to invoke the object's non-existent draw( ) method. In this design methodology, there is a de facto contract between the mouse class and the shape class and these contracts are made possible by the powerful object-oriented design technique called polymorphism.

A Description of Polymorphism

Many people consider polymorphism the cornerstone of O-O design. Designing a class for the purpose of creating totally independent objects is what O-O is all about. In a well-designed system, an object should be able to answer all the important questions about itself. As we have seen, an object should be responsible for itself. This independence is one of the primary mechanisms of code reuse.

As stated in earlier columns, polymorphism literally means many shapes—which makes using a Shape example seem appropriate. When a message is sent to an object, the object must have a method defined to respond to that message. In an inheritance hierarchy, all subclasses inherit the interfaces from their superclass. However, because each subclass is a separate entity, each might require a separate response to the same message.

As an example, consider the class mentioned in the previous section, called Shape. This class has a behavior called draw( ). In this description, all possible shapes will contain a draw( ) method as illustrated in Figure 1.

Figure 1: The Shape class hierarchy.

Grouping entities such as circles and squares into a high-level category like shapes has its advantages; however, when you tell somebody to draw a shape, the first question they ask is likely to be, "What shape?" Simply telling a person to draw a shape is too abstract (in fact, as you will see, the draw( ) method in Shape contains no implementation). You must specify the specific shape that you mean. Rather than including all possible draw( ) implementations in a Shape class, you provide the actual implementation in Circle and other subclasses. Even though Shape has a draw( ) method, Circle overrides this method and provides its own draw( ) method. Overriding basically means replacing an implementation of a parent with your own.

Let's revisit the Shape UML diagram that we saw in an earlier column (see Figure 2).



Click here for a larger image.

Figure 2: The Shape UML draw() Model

Polymorphism is one of the most elegant uses of inheritance. Remember that a Shape cannot be instantiated. It is an abstract class. However, Rectangle and Circle can be instantiated because they are concrete classes. Rectangle and Circle are both shapes; however, they obviously have some differences.

Another good example involving shapes regards calculating areas. Because rectangles and circles are both shapes, their areas can be calculated. Yet, the formula to calculate the area is different for each. Thus, the area formulas cannot be placed in the Shape class. As you can see in the UML model in Figure 2, all implementations of the various getArea( ) methods are placed in the subclasses. Each shape is responsible for calculating its own shapes.



Click here for a larger image.

Figure 3: The Shape UML getArea() Model

This is where polymorphism comes in. The premise of polymorphism is that you can send messages to various objects, and they will respond according to their object type. For example, if you send the message getArea( ) to a Circle class, you will invoke a completely different method than if you send the same getArea( ) message to a Rectangle class. This is because both Circle and Rectangle are responsible for themselves. If you ask Circle to return its area, it knows how to do this. If you want a circle to draw itself, it can do this, too. A Shape object could not do this even if it could be instantiated. Notice that, in the UML diagram, the getArea( ) method in the Shape class is italicized. This designates that the method is abstract.

Now, take a look at some code. Imagine that there are four classes: the abstract Shape class, and concrete Circle, Rectangle, and Star classes. Here is the code:

public abstract class Shape{
   public abstract void draw();
}
public class Circle extends Shape{
   public void draw() {
      System.out.println("I am drawing a Circle");
   }
}
public class Rectangle extends Shape{
   public void draw() {
      System.out.println("I am drawing a Rectangle");
   }
}
public class Star extends Shape{
   public void draw() {
      System.out.println("I am drawing a Star");
   }
}

Notice that there is only one method for each class: draw( ). Here is the important point regarding polymorphism and an object being responsible for itself: The concrete classes themselves have responsibility for the drawing function. The Shape class does not provide the code for drawing; the Circle, Rectangle, and Star classes do this for themselves. Here is some code to prove it:

public class TestShape {
   public static void main(String args[]) {
      Circle circle = new Circle();
      Rectangle rectangle = new Rectangle();
      Star star = new Star();
      circle.draw();
      rectangle.draw();
      star.draw();
   }
}

The test application TestShape creates three classes: Circle, Rectangle, and Star. To actually draw these classes, TestShape simply asks the individual classes to draw themselves:

circle.draw();
rectangle.draw();
star.draw();

When you execute TestShape, you get the following results:



Click here for a larger image.

Figure 4: Running the TestShape Application

This is polymorphism at work. What would happen if you wanted to create a new shape, say Triangle? Simply write the class, compile it, test it, and use it. The base class Shape does not have to change—nor does any other code:

public class Triangle extends Shape{
   public void draw() {
      System.out.println("I am drawing a Triangle");
   }
}

A message can now be sent to Triangle. And even though Shape does not know how to draw a triangle, the Triangle class does:

public class TestShape {
   public static void main(String args[]) {
      Circle circle = new Circle();
      Rectangle rectangle = new Rectangle();
      Star star = new Star();
      Triangle triangle = new Triangle ();
      circle.draw();
      rectangle.draw();
      star.draw();
      triangle.draw();
   }
}



Click here for a larger image.

Figure 5: Adding the Triangle

To see the real power of polymorphism, you can actually pass the shape to a method that has absolutely no idea what shape is coming—just like with the drawing application example that you started out with where the mouse doesn't know the exact shape that it is supposed to draw. Note that in the following code, the application is calling a method called drawMe( ) that requires a single parameter that is defined as Shape. Thus, any valid Shape (actually, any class that inherits from Shape) can be passed to this method.

public class TestShape {
   public static void main(String args[]) {
      Circle circle = new Circle();
      Rectangle rectangle = new Rectangle();
      Star star = new Star();
      drawMe(circle);
      drawMe(rectangle);
      drawMe(star);
   }
   static void drawMe(Shape s) {
      s.draw();
   }
}

In this case, a circle, rectangle, and star object can be passed to the drawMe() method that can handle any valid Shape—even one you add later.

Compiler Enforcement

Finally, it is important to identify that a very powerful aspect of object-oriented development is that the language actually enforces certain rules via the compiler. The use of an abstract class provides the mechanism for the enforcement of the contract concept that you saw earlier. Consider the possibility that the Circle class inherits from Shape, yet fails to implement the draw( ) method.

public class Circle extends Shape{
   .. no draw() method
}

When this code is compiled with the Shape class above, the compiler produces the following error:

C:\TestShape\Circle.java:1: Circle should be declared abstract;
                            it does not define draw( ) in Shape
public class Circle extends Shape{
^
1 error

Thus, the Java compiler acts as an enforcer so that there can be no shape defined that does not provide a draw( ) method.

Conclusion

This column has focused on the key object-oriented concept of object responsibility. You started off by examining the mantra of "an object must be responsible for itself." In a nutshell, the concept of object responsibility and autonomy are key issues when designing a useful and secure object. The fact that objects are moved over networks makes object responsibility and autonomy even more important. One of the benefits of using an object-oriented language is that the compiler itself can enforce certain aspects pertaining to object responsibility through the use of polymorphism. Next month, you will explore other characteristics of proper object design.

References

  • Coad, Peter, and Mark Mayfield: Java Design. Object International, 1999.
  • Meyers, Scott: Effective C++. Addison-Wesley, 1992.
  • Gilbert, Stephen, and Bill McCarty: Object-Oriented Design in Java. The Waite Group, 1998.

About the Author

Matt Weisfeld is an Assistant Professor at Cuyahoga Community College (Tri-C) in Cleveland, Ohio. Matt is a part 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.

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 the basic object-oriented concepts before jumping into the code.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date