Data abstraction is not a new concept: most people already practise it to some degree, probably without realizing it. The idea is, you define a data type and a set of operations on it. All that is visible to the rest of the world is the abstract behaviour of the data type; details of how that behaviour is implemented are hidden, so that they can be changed as necessary without impacting any client code.
For example, consider the definition of a lookup table. Basic operations you might provide are: create a table, put something into it, find something in it. It might be implemented internally as a binary tree, or as a hash table, or as something else entirely--and different implementations might be appropriate in different situations. Client code shouldn't know, or care.
This is where the OOP concept of "inheritance" only serves to confuse things: the fact that a type depends on (or, in OOP parlance, "inherits from") another type, is an implementation matter, that is no concern of the client. Viewed in this way, inheritance is not a very useful form of dependency at all: there are much more powerful ways of making use of other types, that don't conform to the strictures of inheritance. And the whole single-versus-multiple inheritance debate becomes irrelevant.
Another way that the OOP approach gets into trouble is the way it does encapsulation--that is, the way in which it hides the internal implementation of an abstract type from its clients. Object-oriented languages tend to do type-based encapsulation, where the internals of each type are hidden from other types. Trouble is, this doesn't map onto real-world problems very well.
Consider the following outline example, written in Modula-2, which is somewhat loosely based on the QuickTime Movie Toolbox API. The idea is to define a "Movie" abstract data type, where each movie can have one or more associated "Track" objects:
DEFINITION MODULE MovieExample;
TYPE
Movie;
Track;
TYPE
TrackTypes = (VideoTrack, SoundTrack);
PROCEDURE NewMovie
(
VAR Result : Movie
);
(* creates a new empty movie object. *)
PROCEDURE NewMovieTrack
(
InMovie : Movie;
TrackType : TrackTypes;
VAR Result : Track
);
(* adds a new track to a movie. *)
PROCEDURE AddDataToTrack
(
TheTrack : Track;
TheData...
);
(* adds some sample data to a track. *)
PROCEDURE PlayMovie
(
TheMovie : Movie
);
(* plays a movie. *)
END MovieExample.
Observe that "Movie" and "Track" are opaque types; the client has no idea what their internal structure is. This would of course be defined in the corresponding implementation module. Also observe that the two types are inextricably bound together: it doesn't make sense to define them as unrelated types.
Modula-2 allows the two opaque types to be encapsulated in the same module. Object-oriented languages typically don't allow this. C++ is a notable exception with its "friends" mechanism, but when you start to need extra mechanisms to get around the limitations of other mechanisms, isn't it time to ask whether you should be looking at a simpler approach instead?