How do we quantify ‘good’? How precise can we be? Common language required to describe this.
Big-picture language
Cohesion:
- Things are in the right place
- Data and behavior together
- Keep connections local
Coupling:
- Independence between modules
- Separation of concerns/information hiding
- Simple interfaces
Cohesion good; coupling bad.
Why is software design hard?
Problem: complexity
Solution: decomposition
- Too big? break it up
- Too many parts? Hide them
- Too many connections? Decouple it
- Changing too much? Abstract it
Methodologies
Top-down:
- Functional decomposition
- Stepwise refinement
- Transaction analysis
Bottom-up:
- Combination of lower-level components: figure out what you need, then combine them
Nucleus-centred (OO):
- Information hiding
- Decide what the critical core (algorithms etc.) is, then build interfaces around it
Aspect-oriented (e.g. security):
- Separation of concerns
Nucleus-Centred Design
Start with the tricky bits.
Hide information; decisions, choices, things that may change in the future (David Parnas).
Identify decision decisions with competing solutions; isolate these details behind an interface i.e. OO-design; state/access methods encapsulated.
Example: Student
Student StudentDialog
name <-------- student
id
display()? display()?
Where should display() go?
If in StudentDialog, allows for separation of concerns.
If in Student, keeps related data and behavior together, increasing cohesion.
Information-Hiding
Encapsulation: drawing a boundary around something.
Information hiding: hiding design decisions from the rest of the system to prevent unintended coupling.
Encapsulation = programming feature; information hiding = design pattern.
Encapsulation Leak
When implementation details get exposed, allowing implementation details to be exposed and private properties to be modified outside of the class, causing inconsistent/invalid states.
// Student
private Set<Enrolment> enrolments;
public Set<Enrolment> getEnrolments() {
// Bad
return enrolments;
// Better: items cannot be added/removed, but setters/getters can still be called on elements
// Deep clone?
return Collections.unmodifiableSet(enrolments);
}
// Course
add(Student student) {
// Bad: set is being modified outside of the Student class
student.getEnrolements().add(new Enrolment(student));
}
Coping with Change
Figure out the solid bits; the invariants, and make them the framework of your program. Hopefully, the problem won’t change.
Find the wobbly bits and hide them away. In other words, make stable abstractions.
Hiding Design Decisions
If you chose something (i.e. multiple options available), hide it:
- Data representations
- Algorithms
- IO format
- Mechanisms (inter-process communication, persistence)
- Lower-level interfaces
A DAO (data-access objects) allows us to define an interface to access and modify objects.
This allows the internal implementation details to be hidden and thus changed as needed.
Open-Closed Principle
Make your system open for extension but closed for modification.
Any new use case should be able to add to the interface without requiring it to be modified.
Tell, Don’t Ask
Decisions based entirely upon the state of one object should be made inside the object itself.
Avoid asking for information from an object and using it to make decisions about that object.
If this is happening, it may be a sign that the data is being stored in the wrong place.
Example:
m = new VendingMachine();
m.vend(3); // vending machine should check and restock itself
if (m.stock < 10) m.reorder(); // asking: bad