Programming Paradigms

Programming Paradigms

Several distinct programming paradigms are recognized by the software industry. The two primary ones are procedural and object-oriented programming. This section provides a brief introduction to these and several other programming paradigms that are in common use today.

Procedural Software

The procedural software paradigm is the most basic of programming models. A procedural program consists of one or more procedures or functions. Every program has a main function which is its starting point. The program hello_world1 is a very simple C program consisting of a single main function that prints out the message "hello world." Since C is a common procedural language, all the examples in this section will be given using actual C code. Unless your program is very simple like the "hello world" example, however, it will have additional functions, alternatively called procedures or subroutines in some languages. These functions can be called from the main function or from other functions. Following is the hello_world1 sample program.

 /* Program hello_world1 */ #include <stdio.h> int main() {  printf("Hello World");  return(0); } 

For those unfamiliar with C, the line

 #include <stdio.h> 

instructs the compiler to include the standard input/output (stdio) include file within the program. This include file contains definitions that are needed to use C's built-in input/output functions. The most basic output function is the "print formatted" (printf) function, which in the above example is printing a simple string.

Now lets look at the same program utilizing a separate function. This program, called hello_world2, is shown below. Modularizing code into separate functions is one of the key techniques used in structured programming models. Before reviewing this code, it will help to tell you that in C, code comments are delimited by the "/*" and "*/" characters . Anything between these character strings is considered a comment and not processed by the compiler.

 /* Program hello_world2 */ #include <stdio.h> int main() {  /*  ** This is the main function  ** It calls the print_message function to do its work  */  print_message();  return(0); } /*************************** ** Function print_message ** ***************************/ void print_message() { printf("Hello World"); } 

While the hello_world2 program is a trivial example of function calls, it still illustrates one of the basic advantages of structured programming gained when you factor your code into separate functions, code reuse. Now that the print_message() function is separated from the main program, it can be reused in other applications. Structured programming has been widely used in the software development industry since Dijkstra first wrote about the harmful effects of "goto" statements in 1968.

Most programs will also use one or more variables. A variable stores data that is used by the program. Variables commonly have a name , a type such as integer or floating point, and a scope. The scope of a variable defines where in the program the variable will be known and accessible. Computer languages handle variable scope in different ways but typically some combination of local and global variables are supported. A local variable is only known within some well defined section of code, such as a block of code or a function. Global variables are typically known throughout the program. For instance, consider the program simple_variables1, shown below. In this program, the variable total is a global variable and is accessible in both the main function and the function add20 . In the function add20, the local variable x is local only to the function. Their value is independent of the local variable of the same name in the main function.

 /* Program simple_variables1 */ #include <stdio.h> int total;      /* a global variable */ int main() {  int x;        /* a local variable */  int status;   /* function return status */  /* Initialize global variables */  total = 0;  /* Initialize local variables */  x = 10;  /* Call function add20 */  status = add20();  /* Add x to the global variable total */  total = total + x;  printf("The total is %d\n", total);  return(0); } /****************** ** Function add20 ** ******************/ int add20() {  int x = 20;        /* initialize local variable */  /* Add x to the global variable total */  total = total + x;  return(0); } 

The use of global variables, as illustrated in the above example, is often considered a poor programming practice because it impedes reusability. The function add20 , for instance, could not be used by another program because it relies on the global variable total to store its results. Another approach to coding this function is presented in the next section on Modular Software.

In the simple example programs shown so far, all functions were kept in a single file. As a program grows in size , you will most certainly want to break up the functions so that each file contains only a single function or a group of closely related functions. For further ease of packaging, the functions in a group of files can be collected into a library. Purely procedural libraries are stateless. A stateless library, or function, does not retain any information from one invocation to another.

Modular Software

Modular software is very similar to procedural software but adds the concept of state to libraries. For instance, a function could be written to add a number to a running total, where the total would be kept in-between invocations of the function. In a procedural language such as C, modular software must either use global variables or some sort of other persistent storage. Initialization of global variables must normally be done outside the function, either in the main function or another initialization function.

When writing modular software using global variables to maintain state it is not possible to write reentrant code. In reentrant code, functions can be called multiple times, possibly from different functions, without concern for losing state. The program modular1, shown below, modifies the previous example program, sample_variables1, by passing total into the function add20 as a parameter instead of using a global variable. The updated total is then passed back to the calling program in the function's return value. In this example, it is up to the calling function to maintain the state of the variable total .

 /* Program modular1 */ #include <stdio.h> int main() {  int total;    /* local variable, used to maintain state */                /* for the add20 function */  int x;        /* a local variable */  /* Initialize local variables */  total = 0;  x = 10;  /* Call function add20 */  total = add20(total);  /* Add x to the local variable total */  total = total + x;  /* Call function add20 again */  total = add20(total);  printf("The total is %d\n", total); } /******************* ** Function add20 ** *******************/ int add20(int my_total) {  int x = 20;        /* initialize local variable */  /* Add x to my_total */  my_total = my_total + x;  /* Return the new total */  return(my_total); } 

From the example above, we start to see some reasons why programmers appreciate object-oriented languages. For instance, when variables and variable names are shared between functions, the code becomes difficult to read. There is no explicit concept of encapsulation or data hiding. The next section introduces the reader to some simple object-oriented programming concepts and starts to explain some of the benefits of object-oriented programming.

Object-Oriented Software

Object-oriented software is a major departure from procedural software. In procedural software, programming modules are based around specific tasks or functions. For instance, a procedural software model of a coffee machine would have functions for filling it with water, adding coffee, brewing the coffee, and so on. The coffee machine would be represented either by global variables shared among all functions or by variables defined in the main function and passed as parameters to lower level functions. In contrast, an object-oriented software program would be designed around a model of the basic object: the coffee machine. Objects, simply put, are just software models of things in everyday life, such as a coffee machine, a car, a tree, and so on. In theory, object-oriented software can be developed using any programming language. Usually, however, someone wanting to develop object-oriented software would use an object-oriented programming language such as C++ or Java that has language level support for objects.

To be considered object-oriented, a programming language should support three minimum characteristics: encapsulation, inheritance, and dynamic binding. Encapsulation is used to implement information hiding and modularity. Inheritance provides a particular mechanism for code reuse and code organization. Dynamic binding allows applications to be programmed to the interface of general classes of objects so that they need not even be recompiled when new kinds of specific objects are added to the system. These characteristics will be further defined as this section continues. First however, some basics characteristics of objects will be discussed.

At this point, some readers may still be confused about the difference between objects and procedures or functions. One of the subtle but most distinguishing characteristic of objects is that an object can have a lifetime greater than the object that created it. For instance, a coffee machine object might create a cup of coffee object. A programmer can delete the coffee machine object in the program and the cup of coffee object can still live on. In a procedural world, when one function calls a second function, the second function only exists within the scope and lifespan of the first function.

In procedural programming, the two basic constructs used to create programs are functions and data structures. As we have seen, data structures may be contained within functions or may be represented by stand-alone global variables. In object-oriented programming, the basic construct is a class. A class contains both function definitions, called methods in object-oriented parlance, and data structures, referred to as fields. To understand object-oriented programming, it is important to understand the difference between classes and objects. A class is the blueprint for creating a certain type of object. Now think of your application program as a factory. Using your blueprint, or class, the factory can create one or more instances of the object. The methods are operations that manipulate the fields to query or change the state of the object.

In the implementation of an object, the object's state is defined by class fields, or instance variables. Instance variables are variables local to the object. An object's behavior is defined by its methods. A method is similar to a function in a procedural language with one big difference. While procedural functions stand alone, methods are always defined as part of a class. A class is a collection of fields and methods which define a particular object. Methods manipulate the instance variables to create a new state. Unless specifically made public and shared, instance variables cannot be accessed directly by other objects. Instead, if another object wants to set or get an instance variable, it must do so by calling methods provided by the object.

Below is a simple example of a class written in the Java language. This code is actually a complete Java application that you can compile and run with any Java compiler. In this example, the class name is CoffeePot, it contains one method called startCoffeePot , and this method has several fields such as capacity and cupsRemaining . In reviewing this example, it may be helpful to refer to the Java language section in Chapter 14. In addition, one of the best tutorial references on object-oriented programming is the Java tutorial available at http://java.sun.com/docs/.

 public class CoffeePot{     /* The method startCoffeePot brews the coffee */     public static void startCoffeePot() {         /* Initialize local variables */         int capacity = 10;         int cupsRemaining = 0;         boolean full = false;         boolean empty = true;         /* Start brewing the coffee */         System.out.println("Brewing " + capacity +                             " cups of coffee...");         /* Coffee ready! */         full = true;         empty = false;         cupsRemaining = 10;     }     /* Here is the main method */     public static void main(String[] args){         System.out.println("Starting to brew the coffee.");         startCoffeePot();         System.out.println("The coffee pot is now full!");     } } 

In addition to fields and methods, many languages also directly support the concept of superclasses and subclasses. For instance, one might define a car class, with subclasses of sports car and family car. Information about the class is maintained in its instance variables. For instance, a car class might have instance fields for max speed, current speed, number of passengers, and color .

As introduced above, classes differ from procedural structures because classes contain methods defining the operations that can be invoked on the object. By contrast, procedural structures contain data structures only and all operations are left to be implemented in functions. By example, a car might have a "set speed" method. Separating an object's interfaces from its implementation make it much simpler to change the implementation without changing the interface. This makes large projects much easier to develop. For instance, suppose ten different subsystems in a car's software had the ability to set the speed of the car. Each would do so by calling the "set speed" method. Now if the developer wants to rename the current speed variable inside the object, no change is necessary in the ten subsystems that call "set speed." In a procedural language, "current speed" might have been stored as a global variable and each of the ten subsystems might have set the variable directly. Thus any change to the variable name or type would require changes everywhere.

Another way object-oriented languages gain functionality is through the concept of inheritance. There are two basic types of inheritance supported by object-oriented languages: implementation inheritance and interface inheritance. The difference between the two is subtle to grasp but very important to understand so we will explain it in detail. An object's "interface" is a contract that states what functionality will be supported by the object. This is the simpler of the two concepts to understand and therefore use. For instance, consider a language that supports interface inheritance. If the sports car class is a subclass of the car class, it would inherit the interfaces of the car class, including the "set speed" method. This makes sense because if a sports car is a type of car, then you should be able to set the speed of a sports car.

On the other hand, consider implementation inheritance. Some object-oriented languages also support implementation inheritance. This is when a subclass can inherit not only its parent's interfaces but also the implementation of those interfaces. This often does not make sense. For instance, the car method for "max speed" may implement a 65 mile per hour max speed. A sports car max speed may be 100 miles per hour and thus implementation inheritance makes no sense in this case. Languages that support implementation inheritance typically allow for conditional inheritance or overriding of default inheritance. This now starts to lead to complicated language syntax and sometimes difficult to read programs. More importantly, it complicates the architecture of your program.

Distributed and Concurrent Software

A program executing on your computer typically consists of a single process. Most modern operating systems are capable of multi-processing and can run more than one program at a time. The operating system contains special privileged processes that controls access to system resources such as the CPU, memory, file system, and network. User applications access these system resources by making function calls to the appropriate operating system libraries. The relationship between the CPU, OS, and user processes is illustrated in Figure 3-3. So far, all the program examples we have discussed ran as a single process on a single host computer. Whether you are programming simple procedural C code or the latest object-oriented Java components , there will probably come a time when your project will require the use of distributed and/or concurrent programming techniques.

Figure 3-3. H/W, OS, and User Processes
graphics/03fig03.gif

Distributed development breaks up a process so that different parts of the process can execute on separate distributed hosts . Chapter 21 discusses several popular methods of distributed programming included CORBA, JavaBeans, and ActiveX. In concurrent or multithreaded programming, a software process is divided into multiple threads that can execute independently and concurrently. Multithreaded programs thus can take advantage of more than one CPU on a multi-CPU host. Even on a single CPU machine, multithreaded programs can benefit from the asynchronous nature of most applications. Chapter 19 discusses multithreaded programming in greater detail.



Software Development. Building Reliable Systems
Software Development: Building Reliable Systems
ISBN: 0130812463
EAN: 2147483647
Year: 1998
Pages: 193
Authors: Marc Hamilton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net