Besides their failure to support ADT's, modular languages suffered from another serious flaw. In most cases, a module cannot be reused in another project exactly in the same way as it was originally designed: the source code or even its interface have to be changed. This leads to the creation of multiple versions which undermines the very idea of reuse.
In order to overcome this second problem, object-oriented languages introduce language constructs to achieve such delta changes (programming by difference) without having to touch the source code of original modules/classes. Subclasses can be derived (through inheritance) from a parent class. Subclasses can add instance variables, and add, augment or override functions/procedures. This makes flexible reuse possible: inheritance allows module adaptations without having to edit source code or give up compatibility.
Ideally, inheritance suffices to accomplish the few necessary changes for reusing a particular class. As a consequence, many adopters of object technology expect that the usage of an object-oriented language alone yields significant improvements in software reusability. This however leads to a naive application of object technology as discussed below.

There are several reasons for the trends shown in the figure. First, reusing single components without any changes does not come for free. One has to locate the appropriate component, understand its interface and determine how to fit it into the system under construction. Furthermore, a(n electronic) catalogue of reusable components and the components themselves have to be maintained. The components have to be built for reuse which is more expensive than for a special purpose.
More worrying is the fact that only a few changes (12%) to a component raise the reuse costs to 55% compared to a from-scratch development of the particular component: not the changes but the required component understanding costs a lot and, without this understanding, changes would obviously be disastrous.
The study corroborates programmers' gut feeling that single-component reuse is almost as expensive as development from scratch. The bottom line is that it does not matter whether languages support adaptations in an elegant way or not. Simply using an object-oriented language instead of a conventional one cannot ensure reusability improvements.
Projects that apply object technology with this naive view are likely to fail. Nevertheless, many software projects still use object-oriented languages in a manner similar to module-oriented languages, with classes as slightly improved modules, resulting in a design that is object-based rather than object-oriented (see an example from from the AOCS Framework Project for an illustration of the difference between the two approaches).
Many successful object-oriented projects take a different approach to reuse based on software frameworks. In the most general sense, a framework is a collection of components with predefined cooperation between them. Frameworks are adapted to a particular application domain and capture an architectural design which is optimized for that domain.
Frameworks predefine most of the overall architecture (ie the composition and interaction of its components) of a system but at the same time allow for customization by providing hooks where some of the default behaviours can be overridden. It is this possibility of flexibly reusing architecture - as opposed to simple source code fragments - that makes frameworks so successful as reuse vehicles.
Experienced software engineers who develop several related applications reuse architectures as a matter of course. Software frameworks are intended to formalize and make explicit this form of architectural reuse. They allow the investment that is made into designing the architecture of an application to be made available across projects and across design teams. The framework approach is well suited to situations where several functionally similar applications must be developed.
In principle, a framework constitutes a fully working application that can be adapted by parameter tuning. In practice, more flexibility is often required and a well-designed framework presents replaceable elements and `hooks' (also called hot-spots) where new elements can be inserted to customize it for a particular application. This concept is illustrated in the figure where the inner boxes represent modules and arrows represent method calls. The yellow area represents the architectural backbone of the application. This remains fixed in one application domain and is provided by the framework. The red blocks are application-specific. They override or otherwise cooperate with framework objects to customize its behaviour. Note the difference with applications built on module/subroutine libraries: in the latter case, the mission specific code typically calls the reused code, in a framework the mission specific code is called by the reused code.

The next figure shows a second view of framework that makes more explicit their function as a basis for the generation of a whole family of applications. A single framework (yellow square in the figure) can generate any application in the framework domain (red squares in the figure). The figure also shows how frameworks and applications exist at two different levels of abstraction. The process whereby a concrete application is derived from the framework is called framework instantiation.
A framework exists to be adapted. Adaptation takes place during the instantiation process when the framework is transformed from a generic artifact modelling a complete domain into a specific application within that domain. The points where adaptation takes place are called hot-spots (another commong terminology is point of adaptation). Hot-spots can take many forms, depending on the behaviour adaptation mechanism. If, for instance, behaviour adaptation is done by class inheritance, a hot-spot could be a method that is overridden or an abstract method that is defined. In component-based systems, hot-spots are often the points where plug-in components are loaded.
The domain-specific design patterns are optimized solutions to recurring architectural problems in the framework domain. Since they encapsulate reusable architectural solutions, and since frameworks are intended as vehicles for architectural reuse, design patterns are normally the heart of a framework. Additionally, design patterns promote architectural uniformity by ensuring that similar problems in different parts of the same application receive similar solutions. This endows the application architecture with a single "look & feel" that makes using and expanding it considerably easier. Design uniformity is an important aspect of architectural reusability and a second important contribution of design patterns to frameworks.
The concrete components are binary units of reuse that implement one or more interfaces and that can be configured for use in a specific application at run-time. Framework components can be of two types: core components and default components. Core components encapsulate behaviours that are common to all applications in the framework domain. They are therefore used by all applications instantiated from the framework. Default components represent default implementations for some of the abstract interfaces in the framework. They encapsulate behaviours that are found in many applications in the framework domain but that are not intrinsic to the framework domain.
Two basic behaviour adaptation mechanisms are used in object-oriented frameworks: inheritance and object composition. The former mechanism is used for static behaviour adaptation, the latter for dynamic behaviour adaptation. An important property of these adaptation mechanisms is that they allow the behaviour of a target class to be adapted without touching its source code. This is an essential characteristic because it allows the behaviour of the target class to be adapted without invalidating its qualification status.
The figure below illustrates the mechanism of behaviour adaptation through inheritance. In the example in the figure, the derived class extends the interface of its parent class by adding one extra operation to it (operation_4) and it modifies one of its existing operations (operation_2) by overriding its original implementation and providing a new one. Adaptation by inheritance implements the so-called principle of programming by difference: if a given class needs to be modified in response to a change in its requirements, this can be done by subclassing it and building the difference between the original implementation and the new implementation into the derived class. Thus, in the example of the figure, the base class is adapted by adding a new method to it and by changing the implementation of one of its operations. The implementation of its other methods is left unchanged and is reused. Note how adaptation by inheritance does not require touching the code of the class to be adapted (the base class in the figure). Thus, a qualification process performed on the original class remains valid and the investment that was made into qualifying the base class is not lost. Only the (usually small) delta-implementation in the derived class needs to be qualified.

The next figure instead illustrates adaptation through object composition. The adaptable class delegates some of its behaviour to an external helper object (see pseudo-code of method T). Adaptation is achieved by loading a specific helper object into the adaptable class at run time. For this purpose, the adaptable class provides a loadHelper method. The behaviour of the adaptable class can be tuned by loading a new version of the helper object. As in the inheritance case, adaptation is achieved without touching the source code of the adaptable class and hence without invalidating its qualification. Adaptation through object composition is usually preferred to adaptation through inheritance because it creates a sharper demarcation between the adaptable class and the adapting class.

The object composition mechanism shown in the previous figure suffers from one serious drawback: it creates a coupling between two concrete classes (both AdaptableClass and Helper are concrete classes). In a framework perspective, and more generally in an adaptation perspective, this should be avoided because it makes independent use of the two classes very difficult. For this reason, object composition is usually implemented through dynamic coupling. The dynamic coupling mechanism is illustrated in the next figure. The adaptable class is still delegating part of its functionality to an external helper object. The helper object however is now represented by an abstract interface. Adaptation is achieved by switching between different concrete classes that implement this abstract interface. The abstract interface improves the decoupling between the adaptable class and the class that is responsible for the adaptation. It now becomes possible to have different implementations of the helper class that are embodied in different concrete classes and which are all compatible with the adaptable class. As in previous cases, adaptation is achieved without touching the source code of the adaptable class and hence without invalidating its qualification process.

The combination of framework and component technology has the potential of bringing the development of software applications in line with the development of hardware systems. In the long run, a commercial market for software components might emerge similar in character to the market for hardware components. Software applications will then come to be built as their hardware counterparts are built: by assembling commercially available components with standardized interfaces. For each application domain, one or more frameworks will provide the architectural backbone and individual applications will be realized by plugging behaviour-tuning components into a selected framework.
This results in a situation where the heart of the application - its software - is designed, developed and tested by persons who are not application specialists. This approach is regarded as flawed because the interface between the software engineers and their application colleagues lengthens development times and introduces a potential for errors through the misunderstanding or misspecification of the software requirements of the application under development.
A software framework is always targeted at a specific application domain. By providing a set of abstractions that are tailored to this domain, the framework makes it possible for application experts to directly construct their software with only minimal support from software specialists and without the need to write extensive source code. The framework approach thus simplifies the development of applications within a certain domain and in so doing it narrows the gap between the software and the application specialists and reduces the scope for errors at their interface.
An excellent overview of software components and related concepts can be found in: