There is really nothing new in OO development. One can trace evey characteristic of OO developmnet to some good programming practice or elegant technique that already existied in bygone days. The main thing that is uniuqe about OO development is the overall package. It is the way those practices are tied together that makes OO development unique and provides a synergy that makes the whole greater than the sum of its parts. In other words, it is about the way good practices paly together.
Nonetheless there are some fundamental characteristics that are crucial to OO development just because of the emphasis placed upon them or they way they play with other practices. A number of authors who should know better claim that one can't do OO development without inheritance and polymorphism. That's basically a crock. Both inheritance and polymorphism are secondary features that are enabled by more fundamental characteristics. So what are the fundamental cahracteristics of OO development? So glad you asked...
Abstraction. This is the biggee. It is absolutely impossible to do OO development without abstracting some problem space. Basic OO technique involves identifiying problem space entitites that are relevant to the problem in hand and then abstracting their intrinsic properties that are necessary to solve the problem in hand. When abstracting properties one does it in terms of responsibilities for knowing something or doing something.
Encapsulation. Encapsulation is the localizes related properties for a single entity. It does so by exposing responsibilities to other entities without exposing how the responsibility is met. This is somewhat more subtle than implementation hiding (below) because supporting a responsibility may not require an implementation at all. For example, to satisfy a responsibility for knowing its age, a Person object could compute it from its DOB and the current date rather than providing a data store. Encapsulation is a generic term that incorporates several other OO ideas like implementation hiding, information hiding, decoupling, logical indivisibility, and cohesion. The best way to think of encapsualtion is as a package or structure for providing other fundamental OO characterisitcs.
Implementation Hiding. While an object exposes its responsibilities, it hides the implementation of How it meets its responsibilities. That is, the responsibility defines What the object knows or does while the implementation defines How it knows or does it. If this sounds hauntingly familair, it should. OO development, particularly OOA, borrowed heavily from requirements analysis. Typically this is achieved through an interface.
Information Hiding. Many people treat information hiding as essentially synonymous with 'implementation hiding' or, alternatively, apply 'implementation hiding' only to behavior responsibilities and apply 'information hiding' to knowledge responsibilities. In my opinion neither is quite correct. Information hiding is concerns hiding information about the object itself. Most of the time that is information about its implementation. However, it can include other information, such as collaborations. As a general rule the client of an object responsibility should not have any notion of who else the object may colalborate with. That is, the client should not have to understand the rules and policies that govern the service object's participation in other relationships. Thus the fact that the relationship may be implemented through a referential attribute is a separate issue.
Cohesion. The notion of cohesion is closely linked to encapsulation and logical indivisibity (below). The basic idea is that a subsystem or object should provide a cohesive view of the underlying problem space entity. Cohesion, though, is more about the semantics of the entity. One abstracts intrinsic properties of the entity without regard to context. In addition, those property abstractions shold be logically related in the context of the problem in hand.
Decoupling. This is a general term that a number of OO characteristics are designed to support. The basic idea is that maintainability is directly linked to the amount of knowledge one entity has of another. The more carnal the knowledge, the more likely that both will have to be touched when reauirements change. In OO development we are most concerned with implementation coupling where a collaboration object depends on How another object does something. However, it also applies to other circumstances, such as two objects having copies of the same data. One can argue that a primary goal of OO development is to minimize coupling among software entities.
Separation of Message and Method. This is one mechanism for providing encapsulation. By separating the message the client sends to an object from the method that responds to it, one hides the information and implementation of the receiving object. This allows the client to generate messages as announcements of what it has done. The OO developer can then route that message to whatever other object should respond to the message. At the OOA/D level this routing can be done at a different level of abstraction than the implementation of the sender's method that generates the message.
Interfaces. This is another mechanism for providing encapsulation. This is the primary mechanism available at the 3GL level because of the procedural message passing and type systems employed to implement the OOPLs. However, the same basic idea applies as for separation of message and method: the interface defines what type properties (responsibilities) are accessible while tne method implementation remains hidden.
[Unfortunately this is an imperfect mechanism because the message is defined as the method signature by the 3GL type system. Since we name methods by what they do the message becomes an imperative by virtue of naming conventions. (In OOA/D one defines the class interface separately from the class rather than as part of its definition so the namespace separation is preserved.) Fortunately this is relatively harmless so long as the OOA/D is done properly. That is, separation of message and method in the OOA/D eliminates dependencies that might creep in if one goes to OOP directly.]
Peer-to-Peer Collaboration. When objects communicate they do so directly on a peer-to-peer basis. There is none of the hierarchical flow of control that characterised "spaghetti code". That is, there are no "high-level" controller objects corresponding to upper nodes in a classic functional decomposition tree that coordinate the sequencing of other object's operations. One way to think of this is that objects correspond to the leaf processes in a functional decomposition tree and they talk directyl to one another.
Logical Indivisibility. Peer-to-peer collaboration can only work if one has a flexible view of logical indivisibility. In OO development logical indivisibility is defined at three levels: subsystem, class, and responsibility. A subsystem encapsulates a single logically indivisible subject matter. A class encapsulates properties from a single logically indivisible problem space entity. A class responsibilitiy encapsulates a single, logically indivisible property. The flexiblity lies in abstraction. At each level the notion of indivisibility depends upon the level of abstraction. The level of abstraction, in turn, depends upon the problem context.
Self-containment. When objects are abstracted from a problem space the properties are supposed to be intrinsic properties that are independent of the context of a particular problem solution. A corollary to that is that object properties are self-contained. That means that the responsibility can be fully specified without reference to behaviors in any other objects. As a practical matter that means that any object responsibility can be exhaustively unit tested without implementing any other object behaviors (or providing an active stub in the test harness). This characteristic is crucial to avoiding hierarchical depenendencies.
Classes. In OO development the intrinsic properties of a problem space entity are abstracted as knowledge and behavior responsibilities. Classes group entities based upon all members sharing a unique suite of responsibilities. So the OO notion of 'class' maps directly into the notion of a set. OO just uses properties to define the members of the set.
[At the OOP level OOA/D classes are mapped into the ubiquitous 3GL type systems. Types are defined in terms of properties that can be accessed. The mapping is 1:1 but it results in a substantial difference in viewpoint. OOA/D classes are about set membership while OOPL type systems are about property access.]
Subclassing. OO subclassing is an extension of the class idea that deals with entities that are rather similar yet different enough to warrant being in different classes. Basically one defines a class for a large group of entities based upon some common set of charateristic properties that they all have. But there may be subsets of those members that each have additional properties. That presents a conundrum if one defines a class as a unique suite of properties common to all of the entities. To get around that the OO approach introduces the notion of specialization where the subsets form their own subclasses where the special properties are in common.
Thus the superclass that is the union of the subclass subsets defines the common properties for all members and the subclasses represent unique suties of specialized properties. This is often referred to as generalization (superclass) and specialization (subclass). However, a unique thing about OO subclassing is that all of the classes in the subclassing relation exist to resolve the properties of a single instance of the root superclass. That is, unlike Data Modeling using Entity Relationship Diagrams for RDB schemas, the superclasses cannot be instantiated independently of the subclasses. This is why the OO subclassing relation is commonly known as an "is-a" relation; a member of a leaf subclass is-a member of every superclass and resolves the properties of every superclass from which it is a direct descendant. [Their are some additional rules for forming OO subclassing relations that further constrain construction compared to Data Modeling. But that's a more detailed issue...]
Inheritance. Inheritance itself is a relatively simple concept. Inheritance is simply a set of rules for resolving the properties of a leaf object in a subclassing relation. The subclassing relation itself is essentially just a Venn Diagram in tree that identifies subsets of members having the same properties. The tree format is used because a single 2D mapping would be, at best, confusing and, at worst, topologically impossible. Basically inheritance is just a set of simply rules for "walking" the tree to collect all the relevant properties for a single member of the root superclass based upon its subclass membership.
Inclusion Polymorphism. In OO development several forms of polymorhism are supported. However, most people are referring to this particular form of polymoprhism with they don't provide a qualifier. Polymorphism in general is about substitution of behaviors in the OO context. When a general behavior responsibility is accessed through a superclass, the descendant subclasses can provide a unique implementation for that behavior. Thus the actual behavior implementation will be different (substituted) based upon which subclass the the actual member in hand belongs to.
Using the word 'implementation' is somewhat misleading here. One usually does not have different implementations of exactly the same semantic behavior responsibility in mulitple subclasses where the implementations all produce the same results. For example, one usually does not implement a generic superclass 'sort' responsibility with an insertion sort in one subclass and an Quicksort in another subclass. Instead one normally has the subclasses implement a different behavior that produces different detailed results for a more generic superclass responsibility.
It is that abililty to substitute behaviors that produce different detailed results that provides great power to inclusion polymorphism. Note, though, that inclusion polymporhism is enabled by inheritance (which provides the rules for resolving substitution) and inheritance is enabled by the special way OO provides subclassing. In addition, inclusion polymorphism could not work well unless side effects were eliminated by self-containment, implementations were hidden, and interfaces provided decoupling. So, while incusion polymoprhism may be fairly ubiquitous, at least at the OOP level, I don't see it as a definitive characteristic.