Encapsulation
Requests are the only way to get an object to execute an operation. Operations are the only way to change an object’s internal data. Because of these restrictions, the object’s internal state is said to be encapsulated; it cannot be accessed directly, and its representation is invisible from outside the object
Signature
Every operation declared by an object specifies the operation’s name, the objects it takes as parameters, and the operation’s return value. This is known as the operation’s signature
Interface
The set of all signatures defined by an object’s operations is called the interface to the object. Objects are known only through their interfaces. There is no way to know anything about an object or to ask it to do anything without going through its interface.
An object’s interface says nothing about its implementation—different objects are free to implement requests differently. That means two objects having completely different implementations can have identical interfaces.
Type
A type is a name used to denote a particular interface. We speak of an object as having the type “Window” if it accepts all requests for the operations defined in the interface named “Window.”
Dynamic Binding
The run-time association of a request to an object and one of its operations is known as dynamic binding.
Polymorphism
Dynamic binding means that issuing a request doesn’t commit you to a particular implementation until run-time. Consequently, you can write programs that expect an object with a particular interface, knowing that any object that has the correct interface will accept the request. Moreover, dynamic binding lets you substitute objects that have identical interfaces for each other at run-time. This substitutability is known as polymorphism.
SubType
An object may have many types, and widely different objects can share a type. Part of an object’s interface may be characterized by one type, and other parts by other types. Two objects of the same type need only share parts of their interfaces. Interfaces can contain other interfaces as subsets. We say that a type is a subtype of another if its interface contains the interface of its supertype.
Mixin
A mixin class is a class that’s intended to provide an optional interface or functionality to other classes. It’s similar to an abstract class in that it’s not intended to be instantiated.
Mixin classes require multiple inheritance:
Class Inheritance vs Interface Inheritance
Class inheritance defines an object’s implementation in terms of another object’s implementation. In short, it’s a mechanism for code and representation sharing. In contrast, interface inheritance (or subtyping) describes when an object can be used in place of another.
Program to an interface, not an implementation.
Don’t declare variables to be instances of particular concrete classes. Instead, commit only to an interface defined by an abstract class.
White-Box Reuse
Class inheritance lets you define the implementation of one class in terms of another’s. Reuse by subclassing is often referred to as white-box reuse. Because subclasses know all the implementation details of parent class.
Black-Box Reuse
New functionality is obtained by assembling or composing objects to get more complex functionality. Object composition requires that the objects being composed have well-defined interfaces. This style of reuse is called black-box reuse. Because each objects relies only on interface and on each other's implementations.
Favor object composition over class inheritance.
Delegation
Delegation is a way of making composition as powerful for reuse as inheritance [Lie86, JZ91]. In delegation, two objects are involved in handling a request: a receiving object delegates operations to its delegate. This is analogous to subclasses deferring requests to parent classes. But with inheritance, an inherited operation can always refer to the receiving object through the this member variable in C++ and self in Smalltalk. To achieve the same effect with delegation, the receiver passes itself to the delegate to let the delegated operation refer to the receiver.
Parametarized Types (Generics)
Another (not strictly object-oriented) technique for reusing functionality is through parameterized types, also known as generics
Framework vs Library
Frameworks thus emphasize design reuse over code reuse, though a framework will usually include concrete subclasses you can put to work immediately.
Framework vs Patterns
Framework is made up of many patterns, but the reverse is never true.
Frameworks are more specialized to a domain. Patterns can be applied to applications of any domain.
Transparent Enclosure
All this leads us to the concept of transparent enclosure, which combines the notions of (1) single-child (or single-component) composition and (2) compatible interfaces. Clients generally can’t tell whether they’re dealing with the component or its enclosure (i.e., the child’s parent), especially if the enclosure simply delegates all its operations to its component.
SOLID principles
S - Single-responsibility Principle O - Open-closed Principle L - Liskov Substitution Principle I - Interface Segregation Principle D - Dependency Inversion Principle
Dependency Inversion
Dependency inversion is a specific form of decoupling where you decouple the higher levels of your system from the lower levels by separating them into libraries and using interfaces. This allows you to replace lower level parts of your system without major rework.
Subtypes vs Subclasses
Subtyping is a form of type polymorphism in which a subtype is a datatype that is related to another datatype (the supertype) by some notion of substitutability, meaning that program elements, typically subroutines or functions, written to operate on elements of the supertype can also operate on elements of the subtype.
If S is a subtype of T, the subtyping relation is often written S <: T, to mean that any term of type S can be safely used in a context where a term of type T is expected. The precise semantics of subtyping crucially depends on the particulars of what "safely used in a context where" means in a given programming language.
Subclassing should not be confused with subtyping. In general, subtyping establishes an is-a relationship, whereas subclassing only reuses implementation and establishes a syntactic relationship, not necessarily a semantic relationship (inheritance does not ensure behavioral subtyping).
To distinguish these concepts, subtyping is also known as interface inheritance, whereas subclassing is known as implementation inheritance or code inheritance.
Class vs Type
It’s important to understand the difference between an object’s class and its type. An object’s class defines how the object is implemented.
The class defines the object’s internal state and the implementation of its operations. In contrast, an object’s type only refers to its interface—the set of requests to which it can respond. An object can have many types, and objects of different classes can have the same type.
Of course, there’s a close relationship between class and type. Because a class defines the operations an object can perform, it also defines the object’s type. When we say that an object is an instance of a class, we imply that the object supports the interface defined by the class.
Languages like C++ and Eiffel use classes to specify both an object’s type and its implementation.
It’s also important to understand the difference between class inheritance and interface inheritance (or subtyping). Class inheritance defines an object’s implementation in terms of another object’s implementation. In short, it’s a mechanism for code and representation sharing. In contrast, interface inheritance (or subtyping) describes when an object can be used in place of another.
It’s easy to confuse these two concepts, because many languages don’t make the distinction explicit. In languages like C++ and Eiffel, inheritance means both interface and implementation inheritance.
Inheritance vs Composition
The two most common techniques for reusing functionality in object-oriented systems are class inheritance and object composition.
The implementation of a subclass becomes so bound up with the implementation of its parent class that any change in the parent’s implementation will force the subclass to change.
Implementation dependencies can cause problems when you’re trying to reuse a subclass. Should any aspect of the inherited implementation not be appropriate for new problem domains, the parent class must be rewritten or replaced by something more appropriate. This dependency limits flexibility and ultimately reusability.
Without multiple inheritance support, if we try to use class inheritace for code reuse, it will lead to combinatorial exlposion of classes.
Object composition is defined dynamically at run-time through objects acquiring references to other objects. Composition requires objects to respect each others’ interfaces, which in turn requires carefully designed interfaces that don’t stop you from using one object with many others. But there is a payoff. Because objects are accessed solely through their interfaces, we don’t break encapsulation. Any object can be replaced at run-time by another as long as it has the same type.
Object composition has another effect on system design. Favoring object composition over class inheritance helps you keep each class encapsulated and focused on one task.
Problems with inheritence
Instance variables instead of passing explicit arguments.
All instance variables are accessible from everywhere.
Hard to ensure impact of change.
Instance variable could be from any of parent.
Instance variable could be set and reset anywhere making it hard to follow the flow of state.
Delegation
Delegation is a way of making composition as powerful for reuse as inheritance. In delegation, two objects are involved in handling a request: a receiving object delegates operations to its delegate.
This is analogous to subclasses deferring requests to parent classes. But with inheritance, an inherited operation can always refer to the receiving object through the this member variable in C++ and self in Smalltalk. To achieve the same effect with delegation, the receiver passes itself to the delegate to let the delegated operation refer to the receiver.
It must now forward requests to the composed instance explicitly, whereas before it would have inherited those operations.
Delegation is an extreme example of object composition. It shows that you can always replace inheritance with object composition as a mechanism for code reuse.
Inversion of Control
With graphical UIs the UI framework would contain this main loop and your program instead provided event handlers for the various fields on the screen. The main control of the program was inverted, moved away from you to the framework.
Dependency Injection
Dependency injection relies on composition, but is a method for achieving Inversion of Control.
This better highlights what’s special about it - that the object doesn’t control the construction of its parts. For example if you had a BlogPost object that has a reference to a DataBase object, in composition it would be perfectly OK to construct that DataBase instance yourself, in your BlogPost constructor - but this is a no-no in dependency injection; you’re supposed to get the DataBase instance from the outside (as a parameter or something).
Why is this important? In general this helps you decouple your code, but in particular it is useful for unit tests - to continue the above example, the unit test could pass in a mock database object, so that without changing a single line of code all database calls can now be intercepted and redirected to memory or something (and test data can be supplied, again without ever touching a network connection, which could make your test fail for reasons that have nothing to do with your code, like somebody tripping over the cable).