Keep it simple:
- Aim for simplicity
- KISS
- YAGNI
Leave decisions to the last responsible moment: delay the decision so that you have the most information.
Refactoring
Leave the world a better place than you found it.
Martin Fowler abridged: refactoring makes small behavior-preserving transformations, each of which are ‘too small to be worth doing’ but cumulatively have a significant effect.
Object-Oriented Design
Data modelling: focus on data internal to the object
Behavior modelling: focus on services provided to the external world
OO models both; just a question of where you start from.
Inheritance: The Dark Side
Mistakes:
- Using inheritance for implementation
- ‘Is a-role-of’
- Becomes
- Over-specialization
- Violating the LSP
- Changing the superclass contract
Principle: if it can change, it isn’t inheritance.
Using Inheritance for Implementation
Favour composition over inheritance.
e.g. instead of stack inheriting from vector, stack should have the stack as a private variable; the data store for the stack.
‘Is a-role-of’
Example inheritance tree:
Person
Student Staff
Postgrad Tutor Admin Lecturer
Professor
What if a person is both a postgrad and tutor?
Instead, have each Person have multiple Roles. In the real world, when a person changes role, the person doesn’t change.
This allows for separation of concerns.
The ‘Becomes’ Problem
e.g. EligibleStudent and IneligibleStudent inherit from Student. What happens if a student becomes ineligible: the object must switch class becomes of a relatively trivial detail.
Just have eligibility as a boolean in the Student class.
Principle: Inheritance isn’t dynamic.
Over-Specialization
Method arguments etc. should use the most general interface/class you can get away with.
Violating the Liskov Substitution Principle
LSP: the behavior of a method shouldn’t change regardless of the subclass of arguments it is given.
e.g. setWidth, setHeight method for a Rectangle. Square is a subclass of Rectangle. Method taking Rectangle as argument could be given a Square: if it sets the width, the height will be set silently; area/perimeter calculations will give unexpected results.
If methods start telling white lies, you start walking along the path to hell.
Single Responsibility Principle
Every module or class should have responsibility over only one part of the functionality - this responsibility should be fully encapsulated.
Why?
- More responsibility = more reasons to change
- Prevent one class/module from knowing too much
Typically found in controllers, initializers.
Interface Segregation Principle
No client should be forced to depend on methods/interfaces it doesn’t use.
- Reduces risk of ‘white lies’ that one day turn out to bite you in the face
- Split interfaces into cohesive, smaller, more specific ones
- Reduce coupling
e.g. iPhone interface can be split into widescreen iPod, phone, internet communicator device interfaces.
Dependency Inversion Principle
1: When high-level modules depend on low-level modules; both should depend on abstractions.
2: Abstractions should not depend on details; details should depend on abstractions.
i.e. high-level objects should not depend on low-level implementations. Use abstractions; no one likes micro-managers.
When things change, you want as little stuff around it to change.
To do this, try to avoid new; explicitly instantiating concrete instances.
Dependency Injection
Passing objects (that conforms to the broadest interface you can get away with) through a constructor rather than creating a concrete instance of it yourself.
SOLID Principles
Single responsibility.
Open-closed principle.
Liskov substitution principle.
Interface segregation principle.
Dependency inversion.