You get a bonus - 1 coin for daily activity. Now you have 1 coin

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Lecture



Modularity — one of the key principles of object-oriented programming that makes it possible to build flexible, scalable systems. It ensures that code is split into independent components, simplifies maintenance and testing, and promotes the reuse of solutions. In today's development world, modularity has become the foundation for architectures that can withstand growing requirements and increasing project complexity.

Modularity in OOP is a principle that states:

every class should form a separate module.

The second [of the rules which I resolved firmly to observe] was to divide each of the difficulties I examined into as many parts as might be necessary in order best to resolve them.
The third was to order my thoughts in a certain way, beginning with the simplest and easiest-to-know objects, and rising gradually, by degrees, to the knowledge of the most complex, assuming an order even among those that do not naturally precede one another.

René Descartes, "Discourse on the Method" (1637)

Software architects mainly use three standard approaches to creating the initial base components (modules).

  • the workflow-based approach (workflow approach)
  • the actor/action approach (actor/action)
  • the “Entity Trap” anti-pattern (Entity Trap) (to be avoided)

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

The workflow-based approach (workflow approach)

Essence: the system is described as a sequence of steps (a workflow) that carries data or a task from an initial state to a final one.

Advantages:

  • Clearly reflects business processes.
  • Convenient for aligning with clients and analysts.
  • Works well for systems where the sequence of operations matters (for example, order processing).

Disadvantages:

  • Can be too linear and fail to capture parallel scenarios well.
  • When the business logic changes, the chain of steps has to be rebuilt.

The actor/action approach (actor/action)

  • Essence: the components of the system are defined through external roles (actors) and their actions. Each actor initiates certain operations, and the system responds.

  • Advantages:

    • Focus on interaction and behavior.

    • Convenient for designing interfaces and APIs.

    • Works well in distributed and asynchronous systems where actors exchange messages.

  • Disadvantages:

    • Requires clear identification of actors and their responsibilities.

    • Can become complicated when there are many roles.

The “Entity Trap” anti-pattern (Entity Trap) (to be avoided)

  • Essence: the design starts by singling out entities (for example, database tables or domain objects), while behavior and processes remain secondary.

  • Why this is an anti-pattern:

    • The system ends up “anemic” — lots of data structures, little logic.

    • Hard to adapt to real business processes.

    • Leads to excessive model detail without an understanding of behavior.

Five Criteria

Extendibility and reusability, the two key quality factors introduced in lecture 1, require a system with a flexible architecture made up of autonomous software components. That is precisely why lecture 1 introduced the term modularity, which combines both factors.

Modular programming used to be understood as assembling programs out of small pieces, usually subroutines. But such an approach cannot ensure genuine extendibility and reuse of a software product unless it also guarantees that the building blocks - the modules - are self-contained and form stable structures. Any sufficiently complete definition of modularity must ensure that these properties are achieved.

Thus, a method for designing a software product is modular if it helps designers build a system made up of autonomous elements with simple, consistent structural relationships between them. The goal of this lecture is to elaborate on this informal definition and to work out exactly what properties a method must have to deserve the name "modular". Our attention will be focused on the design stage, but all the ideas apply equally well to the earlier stages of analysis and specification, and to the later stages of implementation and maintenance.

Let us look at modularity from several angles. We will introduce a set of additional properties: five criteria, five rules, and five principles of modularity that, taken together, ensure that the most important requirements placed on a modular design method are met.

For a practicing software developer, principles and rules matter just as much as criteria. The difference lies only in the causal relationship: the criteria are mutually independent (a method may satisfy one of them while at the same time conflicting with the rest), whereas the rules follow from the criteria, and the principles follow from the rules.

One might expect this lecture to begin with a detailed description of what a module looks like. But that is not the case, and there are good reasons for it. The task of this lecture and the next two is to analyze the properties that a properly designed modular structure must possess. We will get to the question of what modules look like at the end of our discussion, not at the beginning. Until we reach that point, the word "module" will simply mean a component into which the system under discussion is divided. If you are familiar with non-OO methods, you have probably thought of the subroutines found in most programming and design languages, or perhaps of the packages of the Ada language and (admittedly under a different name) of Modula. Finally, in later lectures our discussion will lead us to the OO form of module - the class. Even if you are already familiar with classes and OO methods, you should still read this lecture to understand the requirements placed on classes - it will help you build them correctly.

A design method that can be called "modular" must satisfy five basic requirements:

  • - Decomposability.
  • - Composability.
  • - Understandability.
  • - Continuity.
  • - Protection.

Decomposability

A design method satisfies the Decomposability criterion if it helps break a problem down into several less complex subproblems, connected by a simple structure and independent enough that work can later proceed on each of them separately.

This process will often be cyclical, since any given subproblem may turn out to be complex enough to require further decomposition.

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Figure 3.1. Decomposition

A consequence of the decomposability requirement is division of labor: once a system has been decomposed into subsystems, the work on them should be distributed among different developers or development teams. This is a difficult task, since it requires limiting the possible interdependencies between subsystems:

  • - Such interdependencies must be kept to a minimum; otherwise the development of each subsystem will be limited by the pace of work on the other subsystems.
  • - These interdependencies must be known: if it proves impossible to draw up a full list of the links between subsystems, then once the project is finished the result will be a set of program elements that may each work fine on their own but cannot be assembled into a complete system meeting the overall requirements of the original problem.

The most obvious example of the method under discussion3.1) , one satisfying the decomposability criterion, is the method of top-down design. Under this method, the developer must start with the most abstract description of the function performed by the system. This representation is then elaborated in successive steps, breaking each subsystem at every step into a small number of simpler subsystems, until elements are reached whose level of abstraction is low enough that they can be implemented directly. This process can be pictured as a tree.

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Figure 3.2. The top-down design hierarchy

A typical counter-example is any method that calls for including a global initialization module in the system being built. Many of a system's modules need initialization - opening files or initializing variables.

Each module must carry out this initialization before it begins performing the operations directly assigned to it. It might seem sensible to gather all such actions for all the system's modules into a single module that initializes everything for everyone at once. Such a module would have good "temporal cohesion" in the sense that all of its actions take place at one stage of the system's operation. But achieving this kind of "temporal cohesion" would require violating the autonomy of the other modules. The initialization module would have to be given access rights to many data structures belonging to various modules of the system, structures requiring specific initialization actions. This means that the author of the initialization module would have to constantly keep track of other modules' data structures and coordinate with their authors. And that is incompatible with the decomposability criterion.

The term "temporal cohesion" comes from a method known as structured design (see the bibliographical notes).

In the object-oriented method, each module must initialize its own data structures on its own.

Modular Composability

A method satisfies the Modular Composability criterion if it supports the development of software elements that can be freely combined with one another to produce new systems, possibly in an environment different from the one for which those elements were originally developed.

Composability defines the process that is the reverse of decomposition: software elements are extracted from the context for which they were originally intended, so that they can be used again in a different context.

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Figure 3.3. Composition

A modular design method eases this process by producing autonomous software elements that are sufficiently independent of the problem originally posed, which makes such extraction possible.

Composability is directly connected with reuse. This criterion reflects an old dream - turning the process of building a software product into a matter of stacking building blocks, so that programs can be built out of factory-made components.

  • - Example 1: Subroutine libraries. Subroutine libraries are built as sets of composable elements. One area where they are used successfully is numerical computation, based on carefully prepared subroutine libraries for solving problems in linear algebra, the finite element method, differential equations, and so on.
  • - Example 2: The conventions of the UNIX operating system's Shell command language. The basic UNIX commands operate on an input stream of sequential characters and produce a result with the same standard structure. The potential for composition is supported by the | operator of the "Shell" command language. The notation A | B denotes the composition of programs. Program A is started first; its results feed into the input of program B, which begins running once program A has finished. This system convention favors the composition of software tools.
  • - Counter-example: Preprocessors. A common way to extend a programming language, and sometimes to work around its shortcomings, is to use a "preprocessor" that accepts input in an extended syntax and maps it into that language's standard form. Typical preprocessors for Fortran's and C support graphics primitives, extended control structures, or database operations. However, such extensions are usually not mutually compatible; this means two such preprocessors cannot be combined, and one must choose, for example, between graphics and a database.

Composability is independent of decomposability. In fact, the two criteria are often in conflict. For example, top-down design, which as already shown satisfies the decomposability criterion, usually produces modules that are not easy to combine with modules obtained from other sources. Under such decomposition, modules tend to be closely tied to the specific requirements that led to their development, and cannot be adapted for use under other conditions. Top-down design gives no guidance on developing modules that satisfy general requirements. It offers no means for such development, and it lets one neither avoid nor even detect redundant code among the modules produced in different parts of the hierarchy.

Both composability and decomposability are part of the requirements placed on a modular design method. A mix of the two design approaches - top-down and bottom-up - is unavoidable. Rene Descartes drew attention to this principle of complementarity almost four centuries ago, as can be seen by comparing the two rules from his Discourse quoted in the epigraph to this lecture.

Modular Understandability

A method satisfies the Modular Understandability criterion if it helps produce a program such that, by reading it, one can understand the content of each module without knowing the text of the others, or, in the worst case, having only looked through a few of them.

The importance of this criterion follows from its effect on the maintenance process of a software product. Almost every maintenance activity, whether unavoidable or not so unavoidable, is bound up with a deep understanding of the program's elements. A method can hardly be called modular if someone reading the program text is unable to grasp its meaning.

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Figure 3.4. Understandability

Like the other four, this criterion applies to modules at any level of system description: analysis, design, or implementation.

  • - Counter-example: sequential dependencies. Suppose some modules are designed so that they function correctly only when run in a certain predetermined order. For example, B may work properly only if run after A and before C, perhaps because these modules are meant to be used in the Unix "pipeline" mentioned earlier: A | B | C. In that case it is apparently hard to understand how B works without understanding how A and C work.

In later lectures, the modular understandability criterion will help us look at two important questions: how to document reusable components, and how to index them so that software developers can easily find them through an appropriate query. Under this criterion, information about a component that is useful for documentation or search should, as far as possible, be contained in the text of the component itself, so that documentation, indexing, or search tools can process the component and extract the required information.

Having the needed information right inside each component is preferable to storing it somewhere else, for example in a database that holds information about components.

Modular Continuity

A method satisfies the Modular Continuity criterion if a small change to the specification of a developed system leads to a change in just one module, or a small number of modules.

This criterion is directly related to the extendibility criterion. As emphasized in the previous lecture, making changes is an inherent part of the software development process. The corresponding requirements on the program will inevitably change over the course of development. Continuity means that small changes will affect only individual modules in the system's structure, not the system as a whole.

The term "continuity" is suggested by analogy with the notion of a continuous function in mathematical analysis. A mathematical function is continuous if (informally) a small change in the argument produces a proportionally small change in the result. In our case, the role of the function is played by the software construction method, which can be regarded as a mechanism that takes a specification as input and returns, as its result, a system satisfying the given requirements:

Software_construction_method: Specification -> System

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Figure 3.5. Continuity

This mathematical term is introduced here only by analogy, since there is no formal notion of the size of a specification or a program. One could devise an acceptable measure for defining "small" or "large" changes to a program, but coming up with a similar definition for specifications is a genuine problem. Still, without claiming any rigor, this intuitive definition captures a necessary requirement for any modular method.

  • - Example 1: named constants3.2) . Good style does not allow constants given as literals in a program. Instead, one should use named constants whose values are given in their definitions (constant in Pascal or Ada, preprocessor macros in C, PARAMETER in Fortran 77, constant attributes in the notation used in this course). If the value changes, only a single change needs to be made - in the constant's definition. This simple but important rule is a sensible way of ensuring continuity, because the values of constants, despite their name, can quite often change.
  • - Example 2: the Uniform Access principle. Another rule calls for a single notation when accessing an object's properties, regardless of whether they represent ordinary or computed data fields.
  • - Counter-example 1: reliance on the physical representation of information. A method in which the programs being developed are tied to the physical implementation of data will lead to constructs that are highly sensitive to minor changes in the environment.
  • - Counter-example 2: static arrays. Languages such as Fortran or standard Pascal, which do not allow dynamic arrays whose bounds become known only at run time, make the evolution of a system considerably harder.

Modular Protection

A method satisfies the Modular Protection criterion if it results in a system architecture in which an abnormal condition occurring during the execution of a module is confined to that module, or, in the worst case, spreads to only a few neighboring modules.

The question of failures and errors is central to software engineering. Here we are concerned with run-time errors caused by hardware interrupts, incorrect input data, or the exhaustion of some needed resource (for example, insufficient memory). The protection criterion is not aimed at preventing or fixing errors, but at a problem directly tied to modularity - the propagation of errors within a modular system.

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Figure 3.6. A violation of protection

  • - Example: validating input data at the source. A method that requires every module reading in data to check its validity is well suited to implementing modular protection.3.3)
  • - Counter-example: undisciplined exceptions. (For more on exception handling, see the chapter on exceptions.) Languages such as PL/I, CLU, Ada, C++, and Java support the notion of an exception. An exception is a situation in which the program cannot continue to execute normally. An exception is "raised" ("raised") by some instruction in a module, and as a result a special signal is sent to the operating system. The exception handler may reside in one or several modules, possibly located in a remote part of the system. The details of this mechanism differ from language to language; Ada or CLU are stricter in this respect than PL/I. Such error-control facilities make it possible to separate the algorithms for the normal case from the algorithms for handling errors. But they must be used with care, so as not to violate modular protection. The chapter on exceptions discusses the design of a disciplined exception mechanism that satisfies the protection criterion.

Five Rules

The criteria examined above give rise to five rules that must be followed in order to achieve modularity:

  • - Direct Mapping.
  • - Few Interfaces.
  • - Small Interfaces (Weak Coupling).
  • - Explicit Interfaces.
  • - Information Hiding (Encapsulation).

The first rule concerns the relationship between the outside system and the software. The next four rules deal with a common problem - how modules communicate with one another. Achieving a good modular architecture calls for a controlled and rigorous method of handling inter-module communication.

Direct Mapping

Every application system aims to meet the needs of some problem domain. If a good model exists for describing that problem domain, it is desirable to ensure a clear mapping from the structure of the problem, as described by the model, onto the structure of the system. This gives rise to the first rule:

The modular structure produced during software construction should remain compatible with the modular structure produced while modeling the problem domain.

This recommendation follows, in particular, from two of the modularity criteria:

  • - Continuity: tracking the modular structure of the problem within the structure of the solution will make evaluation easier and limit the impact of changes.
  • - Decomposability: if some work has already been done analyzing the modular structure of the problem domain, that can serve as a good starting point for dividing the program into modules.

Few Interfaces

The Few Interfaces rule limits the total number of communication channels connecting the modules of a system:

Every module should communicate with as few other modules as possible.

Communication between modules can take many forms. Modules may call one another (if they are procedures), share data structures, and so on. The Few Interfaces rule limits the number of such connections.

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Figure 3.7. Types of inter-module connection structures

In a system made up of n modules, the number of inter-module connections should be much closer to the minimum value n-1, as shown in figure (A), than to the maximum n (n - 1)/2, as shown in figure (B).

This rule follows, in particular, from the continuity and protection criteria: if there are too many interconnections between modules, the effect of a change or an error can spread to a large number of modules. It is also related to the composability criterion (for a module to be usable in a new software environment, it must not depend on too many other modules), as well as to understandability and decomposability.

Option (A) in the last figure shows how to achieve the minimal number of connections, n-1, by means of a highly centralized structure: one main module, with all the others communicating only with it. But there are far more "democratic" structures, such as (C), that contain almost the same number of connections. In this scheme, each module communicates directly with its two nearest neighbors, and there is no central authority. This approach to program construction may at first seem a little surprising, since it does not fit the traditional top-down design model. But it can lead to reliable, extendible solutions. This is exactly the kind of structure that the OO method, applied sensibly, tends to produce.

Small Interfaces (Weak Coupling)

The Small Interfaces rule concerns the amount of information exchanged, not the number of connections:

If two modules communicate with each other, they should exchange as little information as possible.

An electrical engineer would say that the communication channels between modules should have limited bandwidth:

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Figure 3.8. A communication channel between modules

The requirement for Small Interfaces follows, in particular, from the continuity and protection criteria.

A particularly notable counter-example is a construct from Fortran, known to some readers as the "garbage common block" ("garbage common block"). A common block in Fortran's is a directive of the form:

COMMON /common_name/ variable1 : variableN.

The variables listed in the block are accessible in every module that contains a COMMON directive with the same common_name. It is not uncommon to find Fortran's programs in which every module contains the very same huge COMMON directive listing all the significant variables and arrays, so that every module can directly access any data in the program.

The trouble that arises here is that any of the modules can misuse the shared data, and the modules end up tightly coupled to one another; as a result, achieving continuity (limiting the spread of changes) and protection (limiting the spread of errors) becomes extremely difficult. Nevertheless, this time-honored technique remains a favorite of many programmers, even though it leads to long nights of debugging.

Developers using languages with nested structures run into the same difficulties. Given the block structure introduced in Algol and supported, in a more limited form, in Pascal, one can "nest" blocks contained within begin ... end pairs inside other blocks. Moreover, each block can introduce its own variables, which are meaningful only within the syntactic scope of that block. For example:

local -- Start of block B1

x, y: INTEGER

do

... Instructions of block B1 ...

local -- Start of block B2

z: BOOLEAN

do

... Instructions of block B2 ...

end -- End of block B2

local -- Start of block B3

y, z: INTEGER

do

... Instructions of block B3 ...

end -- End of block B3

... Instructions of block B1 (continued) ...

end -- End of block B1

Variable x is accessible to all the instructions in this program fragment, whereas the scopes of the two variables named z (one of type BOOLEAN, the other of type INTEGER) are confined to blocks B2 and B3 respectively. Like x, variable y is declared at the level of block B1, but its scope does not include block B3, where another variable with the same name and the same type locally takes priority over the nearest enclosing variable y. In Pascal's, this kind of block structure exists only for blocks associated with subprograms (procedures and functions).3.4)

Given block structure, the equivalent of Fortran's "garbage" common block is declaring all variables at the very top (global) level. In C-based languages, the equivalent is declaring all variables external. (On clusters, see the course "Fundamentals of Object-Oriented Design". An alternative to nesting is discussed in the section "The architectural role of selective exports (selective exports)".)

Using a block structure is an appealing idea, but it can lead to a violation of the Small Interfaces rule. For this reason we will refrain from using it in the object-oriented notation developed later in this course. The Simula language - an object-oriented derivative of Algol's - supports a block structure for classes. Experience with it has shown that the ability to create nested classes is unnecessary given certain facilities provided by the inheritance mechanism. The structure of object-oriented software has three levels: a system is a set of clusters; a cluster is a set of classes; a class is a set of components (attributes and routines). Clusters, which are more of an organizational device than a linguistic construct, can be nested, letting a project manager structure a large system into as many levels as needed; but classes, like components, have a flat, single-level structure, since nesting at either of these levels would lead to needless complexity.

Explicit Interfaces

The fourth rule is another step toward tightening the totalitarian regime of the module society: it is not enough that any negotiation be limited to just a few participants and be kept brief; such negotiations must also be public and conducted in the open!

Any communication between two modules A and B must be obvious and must be reflected in the text of A and/or B.

Behind this rule lie the criteria of:

  • - Decomposability and composability. If a module needs to be broken down into several submodules, or combined with other modules, any external connection must be clearly visible.
  • - Continuity. It must be obvious which elements could be affected by a possible change.
  • - Understandability. How can one interpret the behavior of module A if its behavior can be indirectly influenced by module B?

One of the problems that arises when applying the Explicit Interfaces rule is that inter-module communication can occur through means other than a procedure call; a source of indirect coupling can be, for example, data sharing:

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Figure 3.9. Data sharing

Suppose module A modifies some data, and module B uses that same data item x. Then A and B end up effectively coupled through x, even though there may be no explicit relationship between them, such as a procedure call.

Information Hiding

The Information Hiding rule can be stated as follows:

The developer of each module must select some subset of the module's properties as the module's official information, accessible to the authors of client modules.

Applying this rule means that every module is known to all the others (that is, to the developers of other modules) through some official description, or so-called public properties.

Of course, that description could be the whole text of the module (the program text, the design text): it does give a correct picture of the module, since it is the module! But the Information Hiding rule states that, in general, this is not required: the description need only include some of the module's properties. The remaining properties should stay non-public, or secret. Instead of the terms public and secret properties, the terms exported and private (hidden) properties are also used. A module's public properties are also known as the module's interface (not to be confused with the user interface of a programming system).

The Information Hiding rule is grounded in the continuity criterion. Suppose that changes occur in some module affecting only its hidden elements and not touching its public properties; then the other modules that reference it, called its clients, will not be affected by these changes. The smaller the public part, the greater the chance that changes to a module will stay confined to its hidden part.

A module that follows the Information Hiding rule can be pictured as an iceberg; only its tip - the interface - is visible to clients.

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Figure 3.10. A module under information hiding

As a characteristic example, consider a procedure that searches by key for attributes stored in a table, such as a personnel file or a compiler's identifier table. This procedure depends heavily on how the table is represented - a sequential array or file, a hash table, a binary or index (B-Tree) tree, and so on. Information hiding means that whatever implementation is chosen for the table does not affect how the procedure is used. Client modules must not suffer from any changes to the program's implementation.

The Information Hiding rule places special emphasis on separating the description of a function from its implementation - what a function does and how it does it are different things. Besides the continuity criterion, this rule is also connected with the decomposability, composability, and understandability criteria. One cannot independently develop the modules of a system, combine existing modules, or understand the behavior of individual modules unless one knows precisely what each of them may (or may not) expect from the other modules.

So which of a module's properties should be public, and which should be hidden? As a rule, the public part should include the functionality specified by the module, while everything related to implementing that functionality should be hidden, shielding other modules from later changes to the program's implementation.

However, this recommendation is imprecise, since no definition has been given for specification or implementation. Indeed, one could be tempted to turn the definition on its head and claim that the specification consists of a module's public properties, while the implementation consists of its hidden properties! The OO approach will provide much more precise guidance, based on the theory of abstract data types. (See, in particular, "Abstract Data Types and Information Hiding".)

To properly understand the meaning of information hiding and apply this rule correctly, it is important to avoid a widespread misinterpretation. Despite its name, information hiding does not mean protecting information in the sense of secrecy - forbidding the authors of client modules from accessing the text of the supplier module. In fact, the authors of client modules have access to all the details that interest them. In some cases it might be sensible to deny them that access, but such a decision, which of course project management can make, does not follow from the information hiding rule. As a technical requirement, information hiding means only that client modules (regardless of whether their authors are permitted access to the hidden properties of supplier modules) must rely only on the public properties of the supplier module. More precisely, it must be impossible to build client modules whose correct functioning depends on hidden information.

Under a formal approach to software development, this definition could be stated as follows. Proving a module's correctness requires making certain assumptions about the properties of its supplier modules. Information hiding means that the proof may rely only on the suppliers' public properties, and in no way on their hidden properties.

Let us again consider the example of a module implementing a table search algorithm. Some client module, which might be part of a spreadsheet system, calls our module to search the table for a particular element. Suppose further that our search algorithm is based on a binary search tree implementation, but this property is hidden - it is not reflected in the interface. The author of the table search module can decide for themselves whether to tell the author of the spreadsheet program how the search algorithm is implemented. That decision belongs to project management or, possibly (in the case of commercially released software), is a marketing-level decision; either way, it has nothing to do with information hiding.

Information hiding means something else: even if the author of the spreadsheet program knows that the search is based on a binary search tree, they should not write a client module that works correctly only with this particular search implementation - one that would stop working if the search algorithm were replaced by some other one, for example, hash-based search.

One reason for the misunderstanding mentioned above is the very term "information hiding" ("information hiding"), which suggests physical protection. In this sense, the term "encapsulation" ("encapsulation"), sometimes used as a synonym for information hiding, would seem preferable; however, our discussion will continue to use the general term "information hiding".

This discussion shows that the key to information hiding lies not in decisions about how access to a module's source text is organized within project management or marketing policy, but in strict language rules that determine what access rights to a module follow from the properties of its source. The next lecture will show that the first steps in this direction have been implemented in "languages with encapsulation" such as Ada and Modula-2. Object-oriented programming technology will lead to a more complete solution to the problem.3.5)

Five Principles

From the preceding rules and, indirectly, from the criteria, five principles of software construction follow:

  • - The Principle of Linguistic Modular Units (Linguistic Modular Units).
  • - The Principle of Self-Documentation (Self-Documentation).
  • - The Principle of Uniform Access (Uniform Access).
  • - The Open-Closed Principle (Open-Closed).
  • - The Principle of Single Choice (Single Choice).

Linguistic Modular Units

The Principle of Linguistic Modular Units states that the formalism used to describe software at various levels (specification, design, implementation) must support modularity:

Linguistic Modular Units Principle

Modules must correspond to the syntactic units of the language used.

The language referred to above may be a programming language, a design language, a language for specifying technical requirements, and so on. In the case of a programming language, modules must be independently compilable.

At any level (analysis, design, implementation), this principle rules out combining a method based on the concept of modularity with a language that lacks the corresponding modular constructs. Indeed, one often encounters firms that, at the design stage, apply certain methodological approaches — for example, using Ada modules — but then implement their designs in a programming language such as Pascal or C, which does not support these approaches. Such an approach violates several of the criteria of modularity:

  • - Continuity: if the boundaries of a module in the final program text do not correspond to the logical decomposition of the specification or the design, then maintaining and evolving the system will make it difficult or even impossible to keep the various levels consistent with one another. A change to the specification can be considered small if it affects the specification of only a small number of modules. To ensure "continuity", there must be a direct correspondence between the specification, the design, and the implementation modules.
  • - Direct mapping: an explicit correspondence must be maintained between the structure of the model and the structure of the solution. This requires an explicit syntactic identification of the conceptual units of the model and the solution, reflecting the decomposition prescribed by the development method.
  • - Decomposability: to break a system down into separate tasks, one must be sure that the solution of each task will result in a clearly bounded syntactic unit; at the implementation stage, these software components must be separately compilable.
  • - Composability: after all, what else besides modules with unambiguously defined syntactic boundaries can be combined with one another?
  • - Protection: only if modules are syntactically delimited can one hope to be able to control the scope of errors.

Self-Documentation

Like the Information Hiding rule, the Principle of Self-Documentation determines how modules should be documented:

Self-Documentation Principle

The developer of a module must strive to ensure that all information about the module is contained within the module itself.

Implementation of this principle is usually hindered by the generally accepted practice of placing information about a module in separate design documents.

The documentation discussed here is internal documentation about software components. User documentation for a released software product may be a separate document, produced as printed text or made available on CD-ROM or web pages on the Internet. As noted in the discussion of software quality, a consequence of the general principle of self-documentation is the currently observed trend toward greater use of interactive online help facilities. (See "On Documentation" Lecture 1 )

The most obvious justification for the need for the Principle of Self-Documentation is the criterion of modular understandability. It appears, however, that more important is the fact that this principle helps to implement the criterion of continuity. If the software and its documentation are regarded as separate objects, it is difficult to guarantee that they will remain consistent — that they will change in step with every change to the system. However, if everything is kept in one place, this, while not providing a full guarantee, will still help maintain consistency.

This principle, harmless at first glance, contradicts much of what is usually recommended for practical application in the software development literature. The prevailing opinion is that a software developer — a software engineer — must do what, apparently, other engineers are obliged to do: produce a kilogram of paper for every gram of product actually created. The suggestion to keep a record of the software development process is not bad advice, but it by no means follows from this that the program and its documentation are different products.

Such an approach ignores a characteristic property of software that has been repeatedly discussed here: its capacity for change. If the program and its documentation are regarded as two separate products, one can soon find oneself in a situation where the documentation states one thing while the program does something else. And incorrect documentation is far worse than no documentation at all.

The major achievement of the last few years has been the emergence of software quality standards. ISO certifications have been developed, along with the "2167" standard and its successors, and the Capability Maturity Model (Capability Maturity Model), proposed by the Software Engineering Institute (Software Engineering Institute). But because they originated from models used in other fields of knowledge, they come with an extensive "tail" of paper documentation. Some of these standards could have had a considerably greater effect on software quality, (besides giving software product administrators a means of justifying themselves in the event of subsequent operational problems) if they had incorporated the Principle of Self-Documentation.

In this course, a consequence of the Principle of Self-Documentation is the method of documenting classes — modules in OO software construction — which calls for including the documentation within the module itself. This by no means implies that the module itself is its own documentation: program text usually contains too much detail (this is precisely the argument in favor of information hiding). The module simply must contain its documentation. (See "Using Class Assertions (assertions) for Documentation" in . See the course "Fundamentals of Object-Oriented Design" and the last two exercises in it.)

With this approach, the software becomes a single software product that provides its various representations, or views (views). One view, suitable for compilation and execution, is the module's complete source text. Another is the documentation, which specifies the module's abstract interface, allowing software developers to create client modules without becoming familiar with the contents of the source module — which corresponds to the Information Hiding rule. Other representations are possible as well.

Uniform Access

Although at first it may seem that the Principle of Uniform Access is aimed only at solving problems related to the notation adopted, in reality it sets a design rule that affects many aspects of OO software development. The principle follows from the criterion of Continuity; it can also be regarded as a special case of the Information Hiding rule.3.6)

Let x be a name used to access some data item, which we will subsequently call an object. Let f be the name of a component (feature) applicable to x. A component is understood to mean some operation; this term will be defined in more detail later. For example, x might be a variable representing a bank account, and f a component giving the current balance of that account (account's current balance). Uniform Access is aimed at resolving the question of what the notation for applying f to x should be, without containing any premature commitment to how f is implemented.

In many design and programming languages, the expression describing the application of f to x depends on the implementation of f chosen by the developer. It may be a property stored together with x, or a method called whenever it is needed. In the example with bank accounts and account balances, both approaches are possible:

  • - A1 The balance of a bank account can be represented as one of the fields of the record describing each account. With this approach, every banking operation that changes the balance must include an adjustment of the corresponding field.
  • - A2 A function can be defined that computes the balance from other fields of this record — for example, fields representing the lists of amounts withdrawn from and deposited into the account. With this approach, the balance value is not stored but is computed on request.

In the conventional notation of languages such as Pascal, Ada, C, C++, and Java, the notation x.f is used for case A1 and f(x) for case A2.

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Fig. 3.11. Two representations of a bank account

The choice between representations A1 and A2 is a trade-off between "space and time": the first saves on computation, the second on memory. The decision to choose one of the variants is a typical example of a decision that the developer changes at least once during the lifetime of a project. Therefore, in order to maintain continuity, it is desirable to have a notation for accessing a component that does not depend on the choice of one of the two representations. If the way x's are implemented is changed at some stage of the project's development, this will not require any changes in the modules that use the call to f.

We have examined an example of the Principle of Uniform Access. In general form, the principle can be formulated as follows:

Uniform Access Principle

All the facilities offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation.

Few languages satisfy this principle. The oldest of them was Algol W, in which both a function call and field access were written as a(x). The first OO language to satisfy the Principle of Uniform Access was Simula 67, which used the notation x.f in both cases. The notation proposed in Lectures 7-18 of this course will support this same convention.

Open-Closed

Any method of modular decomposition must satisfy the semaphore principle: Open-Closed:

Open-Closed Principle

Modules should be both open and closed.

The contradiction is only apparent, since the terms correspond to different goals:

  • - A module is said to be open if it is still available for extension. For example, it should be possible to extend the set of operations it performs, or add fields to its data structures.
  • - A module is said to be closed if it is available for use by other modules. This means that the module (its interface, from the point of view of information hiding) already has a strictly defined, final description. At the implementation level, a module being closed means that it can be compiled, stored in a library, and made available for use by other modules (its clients). At the design or specification stage, closing a module means that it has been approved by management, entered into the official repository of approved project software elements — the project baseline — and its interface has been published for the benefit of the authors of other modules.

The need to close modules and the need to leave them open arise from different causes. For software developers, the natural state of a module is to be open, since it is almost impossible to foresee in advance all the elements — data, operations — that may be needed in the course of creating the module. Developers therefore try to keep the software flexible enough to allow for later changes and additions. But it is necessary, especially from the project manager's point of view, to close modules. In a system consisting of many modules, most modules are dependent on one another. For example, a user-interface module may depend on a parsing module (parsing module) — a syntax analyzer — and on a graphics module. The syntax analyzer may depend on a lexical analysis module, and so on. If a module is not closed until there is certainty that it already contains all the necessary components, it will be impossible to complete the development of a multi-module program: each developer will be forced to wait for all the others to finish their work.

With traditional methodology, the two goals just considered turn out to be incompatible. Either the module remains open, which prevents everyone else from using it, or it is closed, in which case any change or addition can set off an unpleasant chain reaction of laborious changes in many other modules that directly or indirectly depend on this original module.

The two figures below illustrate a situation in which it is difficult to reconcile the need for open and closed states of a module. In the first figure, module A is used by client modules B, C, D, which may themselves have their own clients — E, F, and so on.

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Fig. 3.12. Module A and its clients

As time passes, the situation changes and new clients appear — F and others — who need an extended or adapted version of module A, which we can call A':

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Fig. 3.13. Old and new clients

With non-OO methods, only two solutions to this problem are possible, both equally unsatisfactory:

  • - N1 Module A can be redone so that it provides the extended or modified functionality required by the new clients.
  • - N2 A can be kept as it is, a copy of it can be made, the name of the copy of the module can be changed to A', and all the necessary rework can be carried out in the new module. With this approach, the new module A' will have no connection whatsoever with the old module A.

The possible catastrophic consequences of solution N1 are obvious. Module A may have been in use for a long time and have many clients, such as B, C, and D. The rework needed to satisfy the requirements of the new clients may violate the assumptions on which the old clients relied when using module A; in that case, changes to A can "trigger" a catastrophic chain of changes in the clients, in the clients of those clients, and so on. For a project manager, this is a genuine nightmare: suddenly, entire parts of software that had long been considered finished and deployed turn out to be reopened, which "triggers" a new cycle of development, testing, debugging, and documentation. How many software project managers would want to see themselves in the role of Sisyphus — condemned forever to roll a stone to the top of a mountain only to watch it roll back down each time — all because of problems caused by the need to reopen previously closed modules.

At first glance, solution N2 seems better: it avoids the Sisyphus syndrome, since it does not require modifying software that already exists (shown in the upper part of the last figure). But in reality, this solution can have even worse consequences, since it merely postpones the hour of reckoning. Let us extrapolate the effect of this solution to a large number of modules — many modifications will be needed, taking a long time. In the end, the consequences turn out to be dreadful: an explosive growth in the number of variants of the original modules, many of which are very similar, though not entirely identical.

For many software development organizations, such an abundance of modules, out of proportion to the number of functions actually performed (many variants that appear different turn out, in essence, to be clones), creates a serious software configuration management problem. This problem is usually tackled by using sophisticated tools. Useful in themselves, these tools try to "cure" the program in situations where the first of the solutions discussed would have been preferable. It is, after all, better to avoid redundancy than to create it.

Configuration management will undoubtedly prove useful, but only if it is possible to find the modules that need to be reopened after changes have occurred while at the same time avoiding recompilation of modules that do not need it. (Exercise E3.6 asks you to work out what the need for configuration management will be in an object-oriented programming environment.)

But how can one obtain modules that are simultaneously open and closed? Is it possible to leave module A and all its clients in the upper part of the figure unchanged, while at the same time providing module A' to the clients in the lower part, avoiding duplication of software? Thanks to the mechanism of inheritance (inheritance), the OO approach provides a particularly elegant contribution to solving this problem.

The mechanism of inheritance is examined in detail in later lectures; here only a general idea of it is given. To resolve the dilemma — modify or redo — inheritance makes it possible to define a new module A' on the basis of an existing module A, stating only the differences between them. Let us describe A' as

class A' inherit

A

redefine f, g, ... end

feature

f is ...

g is ...

...

u is ...

...

end

where the feature clause contains both the definition of new components specific to A', such as u, and the redefinition of those components (such as f, g, ...) whose representation in A' differs from what they had in A.

To illustrate inheritance graphically, an arrow is used from the "heir" (heir) (the new class A') to the "parent" (parent) (class A):

3. Modularity in Object-Oriented Programming: Its Essence and Methods of Decomposition

Fig. 3.14. Adapting a module to new clients

Thanks to the mechanism of OO inheritance, developers can pursue a much more consistent approach to software development than was possible with earlier methods. One way of describing the Open-Closed principle and the OO methods that follow from it is to view them as organized hacking. "Hacking" here is understood as a slipshod (slipshod) approach to assembling and modifying a program (and not at all as unauthorized, and certainly impermissible, intrusion into computer networks). A hacker may be considered a bad person, but his intentions are often pure. He may spot a useful piece of a program that is almost suitable for meeting current needs, needs far exceeding those envisioned when the program was originally developed. Inspired by the laudable wish not to recreate what can be reused, our hacker begins modifying the program's source text, adding facilities to it for performing new tasks. Of course, such an impulse is not bad in itself, but the result is often that the program becomes "cluttered" with numerous expressions of the form: if(this_special_case) then. After several repetitions, possibly carried out by different hackers, the program begins to resemble a slice of Swiss cheese left too long in the August heat (the tastelessness of this metaphor is justified by how well it reproduces the appearance, in such a program, of both "holes" and "growths").

This organized form of hacking makes it possible to adapt to the changing structure of the problems being solved without violating the consistency of the original version.

A brief warning: what is being proposed here is not unorganized hacking. In particular:

  • - If it is possible to rewrite the original program so that, without excessive complication, it can satisfy the needs of several kinds of clients, then this should be done.
  • - Neither the Open-Closed principle nor redefinition through the inheritance mechanism can be used to cope with design flaws, let alone bugs in the program. If something is wrong in a module, it should be fixed immediately in the original program, rather than trying to work around the resulting problem in a derived module. A possible exception to this rule is the case of a faulty program that one is not permitted to modify. The Open-Closed principle and the programming techniques associated with it are intended for adapting "healthy" modules — that is, modules which, although they cannot solve certain new problems, do meet strictly defined requirements in the interest of their clients.

Single Choice

The last of the five principles of modularity can be regarded as a consequence of both the Open-Closed principle and the Information Hiding rule.

Before examining the Single Choice principle in detail, let us look at a typical example. Suppose we are building a system for working with a library (in the non-programming sense of the word: a collection of books and other publications, not program modules). This system will process data structures representing various publications. We can declare the corresponding type in Pascal-Ada syntax:

type PUBLICATION =

record

author, title: STRING;

publication_year: INTEGER

case pubtype:(book, journal, conference_proceedings) of

book:(publisher: STRING);

journal:(volume, issue: STRING);

proceedings:(editor, place: STRING) -- Conference proceedings

end

Here we have used a "record type with variants" (record type with variants) to describe sets of data structures with fields, some of which (in this example author, title, publication_year) are common to all cases, while others are specific to particular data variants.

The use of a particular syntactic construct is not essential here. The programming languages Algol 68 and C provide the same capability by means of the "union" (union) type. A union type is a type T defined as the union of previously existing types A, B, ...: a value of type T is either a value of type A or a value of type B, .... The advantage of record types with variants is that each variant is explicitly associated with a tag (tag), for example book, journal, conference_proceedings.

Let A be the module that contains the type declaration described above. As long as module A is considered open, fields can be added to it or new variants introduced into it. But once module A is handed over to clients, it should be closed, which by default means that all the essential fields and variants have already been listed in it. Now let B be a typical client of module A. B will manipulate publications through some variable, for example:

p: PUBLICATION

In order to perform any useful actions using p, it is necessary to explicitly distinguish the various cases:

case p of

book:... Instructions which may access the field p.publisher...

journal:... Instructions which may access fields p.volume, p.issue...

proceedings:... Instructions which may access fields p.editor, p.place...

end

Here the case selection statement from Pascal and Ada proved convenient; its syntax mirrors the definition of the record type with variants. In Fortran's and C this can be imitated by repeated use of the unconditional goto statement (switch in C). In these and other languages, the same result can be obtained using nested conditional statements (if ... then ... elseif ... elseif ... else ... end).

It should be noted that, regardless of the syntactic construct used, in order to make such a choice, every client module must know the complete list of representation variants for a publication supported by module A. The consequences of this are not hard to foresee. A moment will come when a new variant is needed — for example, technical reports from companies and universities. It will then be necessary to extend the definition of the PUBLICATION type in module A to take the new case into account. This is entirely logical and unavoidable: if the definition of the notion of a publication has changed, the corresponding type declaration must be updated too. It is, however, much harder to find a justification for the other consequence: any client of module A, such as B, will also require updating if it used the structure discussed above, based on the complete list of cases for p. And this will obviously be the case for most clients.

So we observe a very dangerous kind of change in the program: a simple and natural addition can trigger a chain reaction of changes in many client modules.

This problem will arise whenever some notion admits multiple variants. Here that notion was "publication" ("publication"), and its initial variants were: book (book), journal article (journal article), conference proceedings (conference proceedings); other typical examples might be:

  • - In a graphics system: the notion of a figure (figure), with variants such as polygon (polygon), circle (circle), ellipse (ellipse), segment (segment), and other basic kinds of figures.
  • - In a text editor: the notion of a user command (user command), with variants such as line insertion (line insertion), line deletion (line deletion), character deletion (character deletion), global replacement (global replacement) of one word by another.
  • - In a compiler for a programming language: the notion of a language construct (language construct), with variants such as instruction (instruction), expression (expression), procedure (procedure).

In any such case, it is necessary to allow for the possibility that the list of variants, given and known at some stage of program development, may later be changed by adding or removing variants. To support this approach to the software development process, a way must be found to protect the structure of the program from the effects of such changes. From this follows the Single Choice principle:

Single Choice Principle

Whenever a software system must support a set of alternatives, their complete list should be known to only one module of the system.

The requirement that the list of choices be known to only one module provides preparation for subsequent changes: when variants are added, only the module that contains this information will need to be updated — that is the essence of single choice. All the other modules, in particular its clients, will be able to continue operating as before.

Thus, as the example of the publication library shows, traditional methods do not provide a solution to the problem, whereas object technology makes it possible to obtain a solution thanks to two techniques associated with inheritance: polymorphism (polymorphism) and dynamic binding (dynamic binding). However, the preliminary discussion given here is not sufficient; these techniques can be properly understood only in the context of the whole method of inheritance. (See "Dynamic Binding" )

The Single Choice principle calls for a few more comments:

  • - According to this principle, the list of possible choices should be known to one and only one module. It follows from the goals of modular programming that it is desirable to have no more than one module possessing this information; but it is equally clear that at least one module must possess it. It is impossible to write a text editor program unless at least one of its components has a list of all the commands supported by that program; for a graphics program, a list of all the figure types; for a compiler, a list of all the language constructs.
  • - Like the other rules and principles discussed in this lecture, the Single Choice principle concerns the distribution of knowledge (distribution of knowledge) within a software system. This issue is truly decisive when it comes to finding extendible, reusable software. To achieve a coherent, reliable software architecture, one must take carefully thought-out steps to limit the amount of information accessible to each module. By analogy with the methods used by certain organizations, this can be called the need-to-know principle (need-to-know): forbid every module access to any information that is not strictly necessary for it to function properly.
  • - The Single Choice principle can be regarded as a direct consequence of the Open-Closed principle. Let us reconsider the example of the publication library in light of the figure illustrating the need for open and closed modules: A is the module containing the original description of the PUBLICATION type; clients B, C are modules that depend on the original list of variants; A' is the improved version of A, offering an additional variant — technical reports (technical reports). (See the second figure in the section "Open-Closed")
  • - This principle can also be understood as a strong form of the Information Hiding principle. The developer of supplier modules, such as A and A', seeks to hide information (concerning the exact list of variants for some notion) from client modules.

Key Concepts

  • - Choosing the right module structure is the key to achieving the goals of its possible reuse and extendibility.
  • - Modules serve both for the decomposition of software (top-down design) and for its composition (bottom-up).
  • - The principles of modularity apply to the specification and design of software as well as to its implementation.
  • - A comprehensive definition of modularity must combine several different viewpoints; the various requirements sometimes turn out to be mutually contradictory, for example decomposition (which encourages top-down design methods) and composition (which favors the use of the bottom-up method).
  • - Controlling the number and form of the connections between modules is the basis for designing a good modular architecture.
  • - The long-term integrity of a modular system's structures requires information hiding, which leads to the need for a strict separation between interface and implementation.
  • - Uniform access frees clients from having to know which internal representation choices have been made in supplier modules.
  • - A module is closed if, thanks to knowledge of its interface, it can be used by client modules.
  • - A module is open if it can still be extended.
  • - For effective project management, modules that are simultaneously open and closed should be supported. But traditional approaches to design and programming do not provide this possibility.
  • - The Single Choice principle requires limiting the spread of complete information about all the variants of some notion.

Bibliographical Notes

The design method known as "structured design" [Yourdon 1979] places special emphasis on the importance of using modular structures. This method was based on an analysis of module "coupling" and "cohesion". But the implicit notion of a module in structured design was based on the traditional concept of a subroutine, which limited the scope of the discussion. The principle of uniform access was originally proposed (under the name "uniform reference") in [Geschke 1975]. In discussing uniform access, mention was made of the Algol W language, a successor to Algol 60 and a predecessor of Pascal (in which some interesting mechanisms were proposed that were not preserved in Pascal's), developed by Wirth and Hoare, and described in [Hoare 1966].

Information hiding was proposed in two seminal papers by David Parnas [Parnas 1972] [Parnas 1972a].

Configuration management tools, which recompile modules affected by changes in other modules based on a detailed list of dependencies between modules, are based on the concepts of the Make utility, originally developed for Unix [Feldman 1979]. Modern utilities — and there are many of them on the software market — have significantly extended the functionality of the original ideas.

Some of the exercises given below ask you to devise metrics for quantitatively assessing the various informal modularity criteria formulated in this lecture. Some results relating to OO metrics can be found in the work of Christine Mingins (Christine Mingins) [Mingins 1993] [Mingins 1995] and Brian Henderson-Sellers (Brian Henderson-Sellers) [Henderson-Sellers 1996a].

Exercises

E3.1 Modularity in programming languages

Study the modular structures of any programming language with which you are well acquainted, and assess how well they satisfy the criteria and principles set out in this lecture.

E3.2 The Open-Closed principle (for Lisp programmers)

Many implementations of Lisp's bind specific functions to their names not statically but at run time. Does this mean that Lisp supports the Open-Closed principle better than statically typed languages?

E3.3 Limitations on information hiding

Can you imagine circumstances under which information hiding should not be applied to the relations between modules?

E3.4 Metrics for modularity (a research report)

The criteria, rules, and principles of modularity have been described in this lecture through qualitative definitions. However, some of them lend themselves to quantitative analysis. These might include:

  • - Modular continuity.
  • - Minimality of interfaces.
  • - Weak coupling of interfaces.
  • - Explicit interfaces.
  • - Information hiding.
  • - Single choice.

Investigate the possibility of devising modularity metrics for assessing how modular the architecture of a software system is with respect to some of these notions. The metrics should be size-independent: increasing the size of a system without changing its modular structure should not change the measures of its complexity (see the next exercise).

E3.5 Modularity of existing systems

Apply the criteria, rules, and principles of modularity from this lecture to assess a system to which you have access. If you solved the previous exercise, apply any of the modularity metrics you proposed.

Can you establish any interdependencies between the results of this analysis (qualitative, quantitative, or both) and assessments of the structural complexity of the system under study, based either on an informal analysis of it or, if possible, on actual measurements of the cost of debugging and maintaining it?

E3.6 Configuration management and inheritance

This exercise assumes knowledge of the inheritance mechanism, described later in this course. It is not worth attempting yet if you have reached this lecture by studying the course sequentially.

The discussion of the Open-Closed principle showed that the absence of inheritance in non-OO methods causes excessive costs in developing configuration management tools, since the desire to avoid reopening closed modules can lead to the creation of too many module variants. Determine what role remains for configuration management tools in an OO environment that has an inheritance mechanism, and more generally, how the use of object technology affects configuration management.

If you are familiar with specific configuration management tools, find out how they interact with the inheritance mechanism and with other principles of OO software development.

Conclusion

Modularity in OOP — is not just a convenient technique, but a strategic approach to designing software systems. It helps developers manage complexity, increases reliability, and speeds up the process of introducing new features. Mastering the principles of modularity opens the way to building robust, elegant architectures that remain relevant even as technologies and business objectives change.

created: 2020-07-21
updated: 2026-05-02
386



Was this answer useful?
Choose a quick rating so we can improve the next answer for you.
How satisfied are you?


Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Object oriented programming"

Terms: Object oriented programming