1
Mekelle University Faculty of Business & Economics Computer Science Department ICT122: Object-Oriented Programming Handout 5 – Object-Oriented Design
Handout Overview This handout describes the object-oriented design process, and the way in which it differs from the conventional design process. The four phases of the object-oriented design process are described and then illustrated with a case study. 1. Object-Oriented Design Before beginning to write a piece of software it is always necessary to design the program first. Designing an object-oriented program involves a different way of thinking to designing a traditional procedural program. 1.1. The object-oriented view of the world In object-oriented programming an application is viewed as a system of interacting objects. The objects each represent a thing in the real world, and interact by sending messages to each other and responding in certain ways when they receive such messages. The object-oriented design process can be divided into four main stages: • Identify the objects • Determine the relationships between the objects • Determine the attribute/behaviours of the objects • Design the driver 1.1.1.Identify the objects In order to identify what objects your program should have you will need to review the problem specification. Remember that classes represent things: a student is a thing, as is a foreign student, etc. Try to work out what things your program will need to deal with and note them down as potential classes.
2
1.1.2.Determine the relationships Next analyse the relationships between these potential classes: for example a foreign student and a student are clearly related. But what type of relationship is it? In Handout 1 three types of relationship were introduced: is-a relationships, has-a relationships and uses-a relationships. Remember that you should use is-a relationships when one object is a type of another object. You should use has-a relationships when one object contains another object. Uses-a relationships are less easy to define and less common. As an example, suppose we have a Clock class that has a public member function currentTime() that returns the current system time. If another object in the program wanted to know the system time it would need to use the Clock class, so it would send a message to a Clock object asking it to execute the currentTime() function. The Clock object would then send a message back indicating the current time. Uses-a relationships are generally implemented by classes sending messages to each other, as in this example. Once you have identified potential classes and the relationships between them, this should help you to sketch out an inheritance hierarchy for your program. 1.1.3.Determine the attributes and behaviours of the objects The operation of an object-oriented program is defined by the way in which its objects interact. By identifying the interactions that are necessary to make a program work correctly we can then decide what behaviours and attributes each object should have. The interactions between objects are defined by their public interfaces. The public interface to an object is the set of public data members and member functions. Think about whether a particular operation is common to an entire hierarchy or is specific to a particular subtype. This should help you to decide which object should contain the operation. If it is common to an entire hierarchy is it a typedependent virtual operation or will it be the same for every type in the hierarchy? Finally ask yourself if the operation should be available to the entire program, restricted to the class hierarchy, or restricted to the individual class (i.e. public, protected or private)? Once we have decided how the objects will interact, we need to think about what responsibilities object will have. For example, if the interaction between object A and object B involves object A sending a message requesting a particular operation from object B, then it is the responsibility of object B to perform that operation. The responsibilities of an object can
3 involve both behaviours (i.e. operations) and attributes (i.e. storing data). These equate to data members and member functions in C++.
1.1.4.Design the driver Finally, now that we have defined the objects, their public interfaces and their behaviours and attributes, we need to design the driver. You can think of the driver as being the glue that binds the objects together, or the main algorithm of the program that makes use of the objects. In C++ the driver corresponds to the main function. 1.1.5.Program design is an iterative process Once you have completed and reviewed these four steps, you can produce a prototype implementation. Often in producing this prototype you may realise that you need to go back to the first two steps and modify your class hierarchy. Program design is always an iterative process: you will go through a number of cycles, or iterations, before you reach a design and implementation you are happy with. Like traditional program design you only really become skilled and confident at object-oriented design by practice, so do not worry too much if it does not come naturally at first. Practice makes perfect …
4
2. Case study – A Vehicle Database The concepts describe above may become clearer if we go through an example. Consider the program requirements for a vehicle database program given below. 2.1. Program Requirements A large company manufactures a range of different vehicles. They would like to have some computer software that stores information about the vehicles they produce. The following information may be useful in designing the program: • The company produces both wheeled vehicles and boats. If a vehicle is a wheeled vehicle it can be either a car or a bicycle. • For all vehicles the company would like to store the following information: the number of people the vehicle can hold, its top speed, model name and cost. • If a vehicle is a wheeled vehicle we should also store the number of wheels. • For all cars, we should store the fuel tank capacity (in litres) and the fuel efficiency (in km/litre). • For all bicycles we should store the number of gears, and a category (mountain bike or racing bike). • For all boats we should store a category (rowing boat, speedboat, dinghy or yacht). • For all vehicles, there should be a function that calculates the registration cost. The actual cost is different for each vehicle type: o Bicycles: 55 Birr o Cars: 10 Birr x fuel tank capacity o Boats: 100 Birr for rowing boats, 1000 Birr for speedboats, 200 Birr for dinghies, 10000 Birr for yachts 2.2. Identifying the Objects The first stage in the design of our program is to identify the objects that our application will consist of. Looking through the program requirements and choosing the nouns is usually a good start. Doing this with our requirements gives us the following potential objects: company vehicle computer software wheeled vehicle boat car bicycle people top speed model name cost wheel fuel tank capacity fuel efficiency gears mountain bike racing bike rowing boat
5 speedboat dinghy yacht It is likely that not all of these nouns will become objects in our program, so let us examine them a bit more closely. A number of the candidates are clearly not relevant to our solution: for example objects based on the company, people and computer software would not have any useful properties or operations for us. 2.3. Interaction/Relationships Between Objects Next we must determine what interactions and relationships there are between our potential objects. A glance through the remaining candidates reveals a number of obvious is-a relationships, a wheeled vehicle is-a vehicle; a boat is-a vehicle; a car is-a wheeled vehicle; a bicycle is-a wheeled vehicle; a mountain bike is-a bicycle; a racing bike is-a bicycle; a rowing boat is-a boat; a speedboat is-a boat; a dinghy is-a boat; a yacht is-a boat; and also a number of has-a relationships, a vehicle has-a top speed; a vehicle has-a model name; a vehicle has-a cost; a wheeled vehicle has-a wheel; a car has-a fuel tank capacity; a car has-a fuel efficiency; a bicycle has-a gears. We know from the discussion in Handout 2 regarding inheritance and composition that generally is-a relationships imply inheritance, and has-a relationships imply composition. However, we have not yet finally decided if all of our candidates will become objects in our program. We need to decide if they have any properties or operations that will be useful for the program, or are they simply data that need to be stored. For example, the requirements state that we need to store the number of gears for all bicycles, but there is no requirement to include any other information or operations about gears, so it would seem that defining a gear object is unnecessary in this case. Similarly, mountain bike and racing bike are types of bicycle, but defining objects of these types would not be useful to us. For boats, on the other hand, we need to store a category and be able to calculate the registration cost. Bearing all this in mind, we can sketch out an initial inheritance hierarchy for our program. Figure 1 shows such a hierarchy. The is-a relationship between boats
6 and vehicles means that the boat class inherits, or derives, from the vehicle class. Similarly wheeled vehicle derives from vehicle, and both car and bicycle derive from wheeled vehicle. Notice that we have now selected five of the candidates to be objects in our solution domain. Many of the remaining candidates will reappear when we start to add data members and member functions to these objects in the next phase.
Figure 1 – An inheritance hierarchy for the vehicle database program 2.4. Determining Attributes and Behaviours Now we must decide what information each of the 5 objects needs to store, and what operations they need to perform. To determine this, we can use the remaining nouns from our list of candidates, and refer to the initial program requirements. For example, for the top speed the requirements state that this value must be stored for every vehicle, so this item should be added as a data member in the vehicle class. All classes that derive from vehicle, whether directly or indirectly, will then inherit this data member. The mountain bike and racing bike items are really just alternative values for a category data member, which should be stored in the bicycle class. The requirements also specify that for every vehicle we should be able to calculate the registration cost. Therefore we should add a member function to the vehicle class. However, the requirements also state that the nature of this calculation will be different for cars, bicycles and boats, so in this case the function should be a virtual function. (Remember from Handout 2 that virtual functions define type-dependent operations in an inheritance hierarchy.) In addition, we do not want the vehicle class to provide an implementation of this function (only car, bicycle and boat should provide implementations) so it should be a pure virtual function. We can now expand our inheritance hierarchy to include attributes (i.e. data members) and behaviours (member functions).
7 Class Vehicle Wheeled Vehicle Car Bicycle Boat
Attributes Number of people, Top speed, Model name, Cost
Behaviours Calculate registration cost (pure virtual function)
Number of wheels Fuel tank capacity, Fuel efficiency Number of gears, Category (racing bike or mountain bike) Category (rowing boat, speedboat, dinghy or yacht)
2.5. Design the Driver The final stage of the design process is to design the driver of the program. For C++ programs, this means deciding what the main function will do, and how it will use the classes that we have defined. In our case, the program requirements do not state what the database should be used for, so this stage is not important. We could, if we wanted to, design a user-interface that lets a user interact with out database of vehicles. However, for the purposes of demonstration we will just design a simple main function that creates a few objects and calls the member functions. 2.6. Code Listing The full code listing for the vehicle database case study is shown below. The source code is split into 3 files: “Vehicle.h” contains the class definitions; “Vehicle.cpp” contains the function bodies; and “VehicleTest.cpp” contains the main function. Notice that we have used information hiding to hide the implementation details of the classes from the rest of the program. The data members are all defined as protected, and public member functions are used to assign values to them. These public member functions define the public interface of the class.
8 “Vehicle.h” class Vehicle { protected: int people; float speed; char *model; float cost; public: void SetPeople (int p) {people = p;} void SetSpeed (float s) {speed = s;} void SetModel (char *m) {model = m;} void SetCost (float c) {cost = c;} virtual float CalcRegistration() = 0; }; class WheeledVehicle: public Vehicle { protected: int wheels; public: void SetWheels (int w) {wheels = w;} }; class Car: public WheeledVehicle { protected: float capacity; float efficiency; public: void SetTankCapacity(float c) {capacity = c;} void SetFuelEfficiency(float e) {efficiency = e;} float CalcRegistration(); }; enum BikeCat {mountain, racing}; class Bicycle: public WheeledVehicle { protected: int gears; BikeCat category; public: void SetGears (int g) {gears = g;} void SetCategory (BikeCat c) {category = c;} float CalcRegistration(); }; enum BoatCat {rowing, speedboat, dinghy, yacht}; class Boat: public Vehicle { protected: BoatCat category; public: void SetCategory (BoatCat c) {category = c;} float CalcRegistration(); };
9 “Vehicle.cpp” #include "Vehicle.h" float Car::CalcRegistration () { return (10 * tank_capacity); } float Bicycle::CalcRegistration () { return 55; } float Boat::CalcRegistration () { float reg = 0; switch (category) { case rowing: reg = 100; break; case speedboat: reg = 1000; break; case dinghy: reg = 200; break; case yacht: reg = 10000; break; }; return reg; }
“VehicleTest.cpp” #include #include "Vehicle.h" main () { Car c; //test out the car class c.SetPeople(4); c.SetSpeed(95); c.SetModel("Lada"); c.SetCost(20000); c.SetWheels(4); c.SetTankCapacity(100); c.SetFuelEfficiency(10); cout << "Registration cost for Lada = " << c.CalcRegistration() << endl; Bicycle b; //test out the bicycle class b.SetPeople(1); b.SetSpeed(20); b.SetModel("Dolphin"); b.SetCost(120); b.SetWheels(2); b.SetGears(15); b.SetCategory(mountain); cout << "Registration cost for Dolphin = " << b.CalcRegistration() << endl;
10 Boat bt; //test out the boat class bt.SetPeople(1500); bt.SetSpeed(45); bt.SetModel("Titanic"); bt.SetCost(1000000); bt.SetCategory(yacht); cout << "Registration cost for Titanic = " << bt.CalcRegistration() << endl; }
11 Summary of Key Points • • • • • • • • • • •
In object-oriented programming an application is viewed as a system of interacting objects. Each object represents a thing in the real world. Objects interact by sending and receiving messages and responding in certain ways when they receive such messages. The object-oriented design process can be divided into four main phases: identify the objects, determine the relationships between the objects, determine the attributes and behaviours of the objects, and design the driver. As objects correspond to things in the real world, a good first attempt and identifying the objects is to look for the nouns in the program requirements. Relationships between objects can be is-a, has-a or uses-a relationships. Is-a relationships imply an inheritance relation between two objects. Has-a relationships imply that composition should be used. Uses-a relationships indicate that the relationship should be modelled by sending messages between the classes. The logical properties of an object are defined by the public interface to the class (i.e. the set of public data members and member functions). The driver is the code that determines what objects will be created and what messages will be sent. In C++ the driver is the main function. Program design is an iterative process. After completing the four stages of the object-oriented design process, it may be necessary to review earlier stages and modify the decisions made.
Note: The full source code listings for the example in this handout can be found on the FBE network server: to access them, open My Network Places, double-click on MU-FBE, then FBE-SERVER and then browse to: Courses\ICT122 – Object-Oriented Programming\src\Handout 5 Notes prepared by: FBE Computer Science Department.
12