OODPP Notes
OODPP Notes
SOLID Principles
S - Single-responsibility Principle
O- Open-closed Principle
L - Liskov Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle
Single-responsibility Principle
A Class should have only one reason to change. It means a class should have only one single job. A class should have
responsibility over a single part of the functionality provided by the software and that responsibility should entirely
encapsulated by the class.
Example: Java Persistence API (JPA) specification. It has one, and only one, responsibility: Defining a standardized way to
manage data persisted in a relational database by using the object-relational mapping concept.
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
It means, a new functionalities can be added with minimum changes in existing code. Extensions add as a new classes.
One approach is use interfaces to allow different implementations which you can easily substitute without changing the
code that uses them. The interfaces are closed for modifications, and you can provide new implementations to extend
the functionality of your software. The interface itself is closed for modification, and you can easily extend it by
providing a new interface implementation.
The main benefit of this approach is that an interface introduces an additional level of abstraction which enables loose
coupling.
If there is a super type called S and there is a reference type of S named s, and there is subtype of S called T and there
is a reference of T named t. If s can be replace with t without having a impact of client code, we are adhering to
Liskov Substitution principle.
The principle defines that objects of a superclass shall be replaceable with objects of its subclasses without breaking the
application. That requires the objects of your subclasses to behave in the same way as the objects of your superclass.
An overridden method of a subclass needs to accept the same input parameter values as the method of
the superclass. That means you can implement less restrictive validation rules, but you are not allowed
to enforce stricter ones in your subclass.
Similar rules apply to the return value of the method. The return value of a method of the subclass
needs to comply with the same rules as the return value of the method of the superclass. You can only
decide to apply even stricter rules by returning a specific subclass of the defined return value, or by
returning a subset of the valid return values of the superclass.
Violation of LSP:
If we depends on specific sub type inside the client code, we are violating LSP. Example if we depends of
<Supertype> instanceOf <Subtype>
Many client specific interfaces are better that one general purpose interface, big interfaces should broke down in to
smaller interface.
Goal: reduce the side effects and frequency of required changes by splitting the software into multiple, independent
parts.
Robert C. Martin’s definition of the Dependency Inversion Principle consists of two parts:
1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
2. Abstractions should not depend on details. Details should depend on abstractions.
High-level modules, which provide complex logic, should be easily reusable and unaffected by changes in low-level
modules, which provide utility features. To achieve that, you need to introduce an abstraction that decouples the high-
level and low-level modules from each other.
An important detail of this definition is, that high-level and low-level modules depend on the abstraction. The design
principle does not just change the direction of the dependency, as you might have expected when you read its name for
the first time. It splits the dependency between the high-level and low-level modules by introducing an abstraction
between them. So in the end, you get two dependencies:
The Open/Closed Principle required a software component to be open for extension, but closed for modification. You can
achieve that by introducing interfaces for which you can provide different implementations. The interface itself is closed
for modification, and you can easily extend it by providing a new interface implementation.
Your implementations should follow the Liskov Substitution Principle so that you can replace them with other
implementations of the same interface without breaking your application.
Low cohesion – functionalities of a module are independent of each other (less re usability, low readability, less
testability)
High cohesion – functionalities of a module are strongly related. (More re usability, more readability, less
testability)[Better]
Tight coupled – A module has many relationship with many other modules (interconnections)
Loosely coupled – A module has fewer relationship with other module. [Better]
https://en.wikipedia.org/wiki/GRASP_(object-oriented_design)
Design Patterns
Factory Pattern
Abstract Factory Pattern
Singleton
Adapter
Decorator
Proxy
Mediator
Visitor
Strategy
Observer
Factory Pattern
Factory Pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows
subclasses to alter the type of objects that will be created.
IF we have base type and more sub types, depending on a condition we have to select one sub type always use factory
class and choose type depending on a conditions that we passed!
Application
Use the Factory Method when you don’t know beforehand the exact types and dependencies of the objects your code
should work with.
Use the Factory Method when you want to provide users of your library or framework with a way to extend its internal
components.
Abstract Factory patterns work around a super-factory which creates other factories. This factory is also called as factory
of factories.
Application:
Here JVM creates the instance of the Singleton when class is loaded JVM creates that instance before any thread
access the static instance variable.
Use synchronization
Using synchronized key word is bog overhead to the application. It may decrease the performance by 100 times. Here the
problem is only first time synchronization is needed. Therefore it will occurs totally unneeded overhead to application.
The solution is use double check locking in Java
Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.
Applications:
1. Use the Adapter class when you want to use some existing class, but its interface isn’t compatible with the rest
of your code.
2. Use the pattern when you want to reuse several existing subclasses that lack some common functionality that
can’t be added to the superclass.
Decorator Pattern
Decorator pattern allows a user to add new functionality to an existing object without altering its structure. This type of
design pattern comes under structural pattern as this pattern acts as a wrapper to existing class. This pattern creates a
decorator class which wraps the original class and provides additional functionality keeping class methods signature
intact.
InputStreame wrapped using InputStreamReader and it is wrapped using BufferedReader to get more functionalities
from java Input Streams.
Applications
1. Use the Decorator pattern when you need to be able to assign extra behaviors to objects at runtime without
breaking the code that uses these objects.
2. Use the pattern when it’s awkward or not possible to extend an object’s behavior using inheritance. (Inheritance
is static. You can’t alter the behavior of an existing object at runtime. You can only replace the whole object with
another one that’s created from a different subclass.)
Observer Pattern
Observer pattern is used when there is one-to-many relationship between objects such as if one object is modified, its
dependent objects are to be notified automatically. Observer pattern falls under behavioral pattern category.
Publisher + Subscriber pattern. Publisher ->subject, subscriber -> observer. Subject notify all the observers when object
modified.
Mediator Pattern
Mediator pattern is used to reduce communication complexity between multiple objects or classes. This pattern
provides a mediator class which normally handles all the communications between different classes and supports easy
maintenance of the code by loose coupling.
The Observer pattern: Defines a one-to-many dependency between objects so that when one object changes state, all
its dependents are notified and updated automatically.
The Mediator pattern: Define an object that encapsulates how a set of objects interact. Mediator promotes loose
coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.