- There should never be more than one reason for a class to change
A class prodivides a single functionality and addresses a specific concern.
Example: A class sending messages to a server. What are possible reasons to change this class?
Protocol may change, message format can change etc. This is what we avoid.
If you have more than one responsibility in the class, you need to separate them.
- Software entities (classes, methods etc.) should be open for extension, but closed for modification.
- Child class object shouldn't alter the behavior/characteristics of a base class.
Overriding doesn't violate this rule. Because think that you have a print method in base class.
If child class overrides it then it prints another thing. But the behavior is the same.
Most popular example is extending square from rectangle. Because of squre width and height is equal, if we use rectangle as a base class when we set square width and height each time we fail.
That's why this relation is not proper in OOP. We create shape class as base.
Clients shouldn't be forced to depend upon interfaces that they don't use.
This is related with interface pollution which is about we shouldn't create large interfaces that
contains unrelated methods.
Signs of Interface Pollution
- Classes have empty method implementation
- Method implementations throw UnsupportedOperationException or similar
- Method implementations return null or default values
We should avoid that.
Write highly cohesive interfaces.
- High level modules should not depend upon low level modules. Both should depend upon abstractions
- Abstractions should not depend upon details. Details should depend upon abstractions.
Low level modules mean that provides functionality that can be used anywhere for ex, writing disk, accept json
High level module means that the functionality that we implement, our business logic.
When we instantiate objects, we tightly coupled to particular implementations.
Think about you are creating a formatter which uses xml. Then in someplace you need to replace it with
json formatter. In this case we create tightly coupled code. So instead of depend on concrete classes,
we need to depend on abstractions.
Instead of instantiating of dependencies, let somebody injects it.
We are not creating instance of dependencies, somebody creates it and gives us.
Its about how objects interact with each other.
Used for one many relations.
- One to many
- Decoupled
- Event handling
- Pub / Sub
- Examples: java.util.Observer, java.util.EventListener
- Subjects
- Observer
- Observable
- Unexpected updates
- Large sized consequences
- Debugging difficult
Observer | Mediator |
---|---|
One to many | one to one to many |
Decoupled | Decoupled |
Broadcast communication | Complex communication |
- Decoupled communication
- Built in functionality
- Used with mediator
Used when we need to represent a state in application
- Localize state behavior
- State object
- Separates what from where
- Open Closed Principle
- Examples: none , there is no a good example in java core api. some people says that iterator is a state pattern.
- Abstract class / interface
- Class based
- Simplifies cyclomatic complexity
- Adding additional states made easier
- More classes
- Similar implementation to strategy
Strategy | State |
---|---|
Interface based | Interface based |
Algorithms are independent | Transitions |
Class per algorithm | Class per state |
- Externalizes algorithms
- Client know different strategies
- Class per strategy
- Reduces conditional statements
Used when you enable the strategy or algorithm to be selected at runtime.
- Eliminate conditional statements
- Behavior encapsulated in classes
- Difficult to add new strategies
- Client aware of strategies
- Client chooses strategy
- Examples: Comparator => it enables client to choose proper stragety for its usage
- Client aware of strategies
- Increases number of classses
Strategy | State |
---|---|
Interface based | Interface based |
Algorithms are independent | Transitions |
Class per algorithm | Class per state |
- Externalizes algorithms
- Client know different strategies
- Class per strategy
- Reduces conditional statements
- Its about how you use or utilize objects.
- It could be something like performance or refactoring or memory utilization.
Structural Patterns are:
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
Its a great pattern for connecting new code legacy code without having to change the working contract that was produced from the legacy code originally.
- Convert interface into another interface
- Legacy
- Examples: Arrays to Lists
- Not a lot
- Multiple adapters
- Do not add functionality consider using decorator patterns instead.
Adapter | Bridge |
---|---|
Works after code is designed | Designed upfront |
Legacy | Abstraction and implementation vary |
Retrofitted | Built in advance |
Provides different interface | Both adapt multiple systems |
- Simple solution
- Easy to implement
- Integrate with legacy
- Can provide multiple adapters
Very similar to Adapter pattern. Tha main difference is that bridge works with new code, whereas the adapter works with legacy code.
- It decouples abstraction and implementation
- To do this use Encapsulation, composition, inheritance
- Changes in abstraction wont affect the client
- Details may not be right
- If you aren't quite sure of what the end product of what you're building will be, the bridge is great for giving us flexibility without breaking things with change
- Examples => Driver, JDBC
- Increases complexity
- Conceptually difficult to plan
Very similar to Adapter pattern. Tha main difference is that bridge works with new code, whereas the adapter works with legacy code.
- Make an API easier to use
- Reduce dependencies on outside code
- Simplify the interface or client usage
- Usually a refactoring pattern
- Examples: java.net.URL
- Typically used to clean up code
- Should think about API design
Facade | Adapter |
---|---|
Simplifies interface | Also a refactoring pattern |
Works with composites | Modifies behavior |
Cleaner API | Provides a different interface |
Is a pattern that minimizes memory used by sharing data with similarly type objects.
- More efficient use of memory
- Large number of similar objects
- Immutable
- Most of the object states can be extrinsic
- Examples: java.lang.String and all other wrapper classes
- Patterns of patterns
- Utilizes a factory
- Encompasses creation and structure
- Complex pattern
- Must understand factory
Flyweight | Facade |
---|---|
Memory optimization | Refactoring pattern |
Optimization pattern | Simplified client |
Immutable objects | Provides a different interface |
Is a pattern that acts as a interface to something else
- Interface by wrapping
- Can add functionality
- Security, simplicity, remote
- Proxy called to access real object
- Examples: java.lang.reflect.Proxy, java.rmi.*
- Complex pattern
- Must understand factory
Flyweight | Facade |
---|---|
Memory optimization | Refactoring pattern |
Optimization pattern | Simplified client |
Immutable objects | Provides a different interface |