LAYERED ARRANGEMENT OF MODULES
The control hierarchy represents the organisation of program components in terms of their call relationships.
Thus we can say that the control hierarchy of a design is determined by the order in which different modules call each other.
Many different types of notations have been used to represent the control hierarchy.
The most common notation is a tree-like diagram known as a structure chart.
In a layered design solution, the modules are arranged into several layers based on their call relationships.
A module is allowed to call only the modules that are at a lower layer. That is, a module should not call a module that is either at a higher layer or even in the same layer.
Figure 5.6(a) shows a layered design, whereas Figure 5.6(b) shows a design that is not layered.
Observe that the design solution shown in Figure 5.6(b), is actually not layered since all the modules can be considered to be in the same layer.
An important characteristic feature of a good design solution is layering of the modules. A layered design achieves control abstraction and is easier to understand and debug.
In a layered design, the top-most module in the hierarchy can be considered as a manager that only invokes the services of the lower level module to discharge its responsibility.
The modules at the intermediate layers offer services to their higher layer by invoking the services of the lower layer modules and also by doing some work themselves to a limited extent.
The modules at the lowest layer are the worker modules. These do not invoke services of any module and entirely carry out their responsibilities by themselves.
Understanding a layered design is easier since to understand one module, one would have to at best consider the modules at the lower layers (that is, the modules whose services it invokes).
Besides, in a layered design errors are isolated, since an error in one module can affect only the higher layer modules.
As a result, in case of any failure of a module, only the modules at the lower levels need to be investigated for the possible error.
Thus, debugging time reduces significantly in a layered design.
On the other hand, if the different modules call each other arbitrarily, then this situation would correspond to modules arranged in a single layer.
Locating an error would be both difficult and time consuming. This is because, once a failure is observed, the cause of failure (i.e. error) can potentially be in any module, and all modules would have to be investigated for the error.
Some important concepts and terminologies associated with a layered design:
Superordinate and subordinate modules:
In a control hierarchy, a module that controls another module is said to be superordinate to it.
Conversely, a module controlled by another module is said to be subordinate to the controller.
A module B is said to be visible to another module A, if A directly calls B.
Thus, only the immediately lower layer modules are said to be visible to a module.
In a layered design, a module should only invoke the functions of the modules that are in the layer immediately below it.
In other words, the modules at the higher layers, should not be visible (that is, abstracted out) to the modules at the lower layers.
This is referred to as control abstraction.
Depth and width:
Depth and width of a control hierarchy provide an indication of the number of layers and the overall span of control respectively.
For the design of Figure 5.6(a), the depth is 3 and width is also 3.
Fan-out is a measure of the number of modules that are directly controlled by a given module.
In Figure 5.6(a), the fan-out of the module M1 is 3.
A design in which the modules have very high fan-out numbers is not a good design. The reason for this is that a very high fan-out is an indication that the module lacks cohesion.
A module having a large fan-out (greater than 7) is likely to implement several different functions and not just a single cohesive function.
Fan-in indicates the number of modules that directly invoke a given module.
High fan-in represents code reuse and is in general, desirable in a good design.
In Figure 5.6(a), the fan-in of the module M1 is 0, that of M2 is 1, and that of M5 is 2.
APPROACHES TO SOFTWARE DESIGN
There are two fundamentally different approaches to software design that are in use today
- Function-oriented design
- Object-oriented design
Though these two design approaches are radically different, they are complementary rather than competing techniques.
The object oriented approach is a relatively newer technology and is still evolving.
For development of large programs, the object- oriented approach is becoming increasingly popular due to certain advantages that it offers.
On the other hand, function-oriented designing is a mature technology and has a large following.
The following are the salient features of the function-oriented design approach:
A system, to start with, is viewed as a black box that provides certain services also known as high-level functions to the users of the system.
In top-down decomposition, starting at a high-level view of the system, each high-level function is successively refined into more detailed functions.
For example, consider a function create-new-library member which essentially creates the record for a new member, assigns a unique membership number to him, and prints a bill towards his membership charge.
This high-level function may be refined into the following subfunctions:
Each of these subfunctions may be split into more detailed subfunctions and so on.
Centralised system state:
The system state can be defined as the values of certain data items that determine the response of the system to a user action or external event.
For example, the set of books (i.e. whether borrowed by different users or available for issue) determines the state of a library automation system.
Such data in procedural programs usually have global scope and are shared by many modules.
The system state is centralised and shared among different functions. For example, in the library management system, several functions such as the following share data such as member-records for reference and updation:
A large number of function-oriented design approaches have been proposed in the past.
In the object-oriented design (OOD) approach, a system is viewed as being made up of a collection of objects (i.e. entities).
Each object is associated with a set of functions that are called its methods.
Each object contains its own data and is responsible for managing it. The data internal to an object cannot be accessed directly by other objects and only through invocation of the methods of the object.
The system state is decentralised since there is no globally shared data in the system and data is stored in each object.
For example, in a library automation software, each library member may be a separate object with its own data and functions to operate on the stored data.
The methods defined for one object cannot directly refer to or change the data of other objects.
The object-oriented design paradigm makes extensive use of the principles of abstraction and decomposition as explained below.
Objects decompose a system into functionally independent modules.
Objects can also be considered as instances of abstract data types (ADTs).
The ADT concept did not originate from the object-oriented approach. In fact, ADT concept was extensively used in the ADA programming language introduced in the 1970s.
ADT is an important concept that forms an important pillar of objectorientation.
Let us now discuss the important concepts behind an ADT.
There are, in fact, three important concepts associated with an ADT
- data abstraction
- data structure
- data type.
We discuss these in the following subsection:
The principle of data abstraction implies that how data is exactly stored is abstracted away.
This means that any entity external to the object (that is, an instance of an ADT) would have no knowledge about how data is exactly stored, organised, and manipulated inside the object.
The entities external to the object can access the data internal to an object only by calling certain well-defined methods supported by the object.
Consider an ADT such as a stack. The data of a stack object may internally be stored in an array, a linearly linked list, or a bidirectional linked list.
The external entities have no knowledge of this and can access data of a stack object only through the supported operations such as push and pop.
A data structure is constructed from a collection of primitive data items. Just as a civil engineer builds a large civil engineering structure using primitive building materials such as bricks, iron rods, and cement; a programmer can construct a data structure as an organised collection of primitive data items such as integer, floating point numbers, characters, etc.
A type is a programming language terminology that refers to anything that can be instantiated.
For example, int, float, char etc., are the basic data types supported by C programming language.
Thus, we can say that ADTs are user defined data types. In object-orientation, classes are ADTs.
Advantage of developing an application using ADTs
Let us examine the three main advantages of using ADTs in programs:
- The data of objects are encapsulated within the methods. The encapsulation principle is also known as data hiding. The encapsulation principle requires that data can be accessed and manipulated only through the methods supported by the object and not directly. This localises the errors. The reason for this is as follows. No program element is allowed to change a data, except through invocation of one of the methods. So, any error can easily be traced to the code segment changing the value. That is, the method that changes a data item, making it erroneous can be easily identified.
- An ADT-based design displays high cohesion and low coupling. Therefore, object- oriented designs are highly modular.
- Since the principle of abstraction is used, it makes the design solution easily understandable and helps to manage complexity.