Specifying attributes and behaviors
We now have a grasp on some basic object-oriented terminology. Objects are instances of classes that can be associated with each other. An object instance is a specific object with its own set of data and behaviors; a specific orange on the table in front of us is said to be an instance of the general class of oranges. That's simple enough, but what are these data and behaviors that are associated with each object?
Data describes objects
Let's start with data. Data typically represents the individual characteristics of a certain object. A class of objects can define specific characteristics that are shared by all instances of that class. Each instance can then have different data values for the given characteristics. For example, our three oranges on the table (if we haven't eaten any) could each have a different weight. The orange class could then have a weight attribute. All instances of the orange class have a weight attribute, but each orange might have a different value for this weight. Attributes don't have to be unique though; any two oranges may weigh the same amount. As a more realistic example, two objects representing different customers might have the same value for a first name attribute.
Attributes are frequently referred to as properties. Some authors suggest that the two terms have different meanings, usually that attributes are settable, while properties are read only. In Python, the concept of "read only" is not really used, so throughout this book we'll see the two terms used interchangeably. In addition, as we'll discuss in Chapter 5, the property keyword has a special meaning in Python for a particular kind of attribute.
In our fruit inventory application, the fruit farmer may want to know what orchard the orange came from, when it was picked, and how much it weighs. They might also want to keep track of where each basket is stored. Apples might have a color attribute and barrels might come in different sizes. Some of these properties may also belong to multiple classes (we may want to know when apples are picked, too), but for this first example, let's just add a few different attributes to our class diagram:
Depending on how detailed our design needs to be, we can also specify the type for each attribute. Attribute types are often primitives that are standard to most programming languages, such as integer, floating-point number, string, byte, or boolean. However, they can also represent data structures such as lists, trees, or graphs, or, most notably, other classes. This is one area where the design stage can overlap with the programming stage. The various primitives or objects available in one programming language may be somewhat different from what is available in other languages. Usually we don't need to concern ourselves with this at the design stage, as implementation-specific details are chosen during the programming stage. Use generic names and we'll be fine. If our design calls for a list container type, the Java programmers can choose to use a LinkedList
or an ArrayList
when implementing it, while the Python programmers (that's us!) can choose between the list
built-in and a tuple
.
In our fruit farming example, so far, our attributes are all basic primitives. But there are implicit attributes that we can make explicit: the associations. For a given orange, we might have an attribute containing the basket that holds that orange. Alternatively, one basket might contain a list of the oranges it holds. The next diagram adds these attributes as well as including type descriptions for our current properties:
Behaviors are actions
Now we know what data is, but what are behaviors? Behaviors are actions that can occur on an object. The behaviors that can be performed on a specific class of objects are called methods. At the programming level, methods are like functions in structured programming, but they magically have access to all the data associated with that object. Like functions, methods can also accept parameters, and return values.
Parameters to a method are a list of objects that need to be passed into the method that is being called. These objects are used by the method to perform whatever behavior or task it is meant to do. Return values are the results of that task. Here's a concrete example; if our objects are numbers, the number class might have an add
method that accepts a second number as a parameter. The first number object's add
method will return the sum when the second number is passed to it. Given an object and it's method name, a calling object can call, or invoke the method on the target object. Invoking a method, at the programming level, is the process of telling the method to execute itself by passing it the required parameters as arguments.
We've stretched our, "comparing apples and oranges" example into a basic (if far-fetched) inventory application. Let's stretch it a little further and see if it breaks. One action that can be associated with oranges is the pick action. If you think about implementation, pick would place the orange in a basket by updating the basket attribute on the orange, and by adding the orange to the oranges list on the Basket. So pick needs to know what basket it is dealing with. We do this by giving the pick method a basket parameter. Since our fruit farmer also sells juice, we can add a squeeze method to Orange. When squeezed, squeeze might return the amount of juice retrieved, while also removing the Orange from the basket it was in.
Basket can have a sell action. When a basket is sold, our inventory system might update some data on as-yet unspecified objects for accounting and profit calculations. Alternatively, our basket of oranges might go bad before we can sell them, so we add a discard method. Let's add these methods to our diagram:
Adding models and methods to individual objects allows us to create a system of interacting objects. Each object in the system is a member of a certain class. These classes specify what types of data the object can hold and what methods can be invoked on it. The data in each object can be in a different state from other objects of the same class, and each object may react to method calls differently because of the differences in state.
Object-oriented analysis and design is all about figuring out what those objects are and how they should interact. The next section describes principles that can be used to make those interactions as simple and intuitive as possible.