Classes
Class Organization
- A class should begin with a list of variables: public static constants, private static variables, private instance variables.
- Public functions should follow this list, and we want to put private utilities that are called upon by a function underneath that function.
- Encapsulation: We want to keep our variables and utility functions private, but we're not fanatic about it.
- Sometimes we need to make them protected so that it can be accessed by a test.
- Loosening encapsulation is always a last resort.
Classes Should Be Small!
-
Similar to the concept discussed with functions.
-
The smaller the better, we shouldn't have to use words like 'and', 'if', 'or', and 'but' as this implies that the class does too much.
-
Follow Single Responsibility Principle; should have only one reason to change.
-
Getting software to work and making software clean are two very different activities.
-
It is logical and reasonable to just focus on making sure the code works, but the problem comes in when you move on to the next thing once it works rather than going back and making it clean.
Cohesion
-
Classes should have a small number of instance variables and each method should interact with one or more of these variables.
-
The reason this is desired is because it creates a tightly coupled unit that is all related and "cohesive".
-
Below is a very cohesive class:
public class Stack { private int topOfStack = 0; List<Integer> elements = new LinkedList<Integer>(); // Only method that doesn't use both instance variables public int size() { return topOfStack; } public void push(int element) { topOfStack++; elements.add(element); } public void pop() throws PoppedWhenEmpty { if (topOfStack == 0) throw new PoppedWhenEmpty(); int element = elements.get(--topOfStack); elements.remove(topOfStack); return element; } }
Maintaining Cohesion Results in Many Small Classes
- In order to maintain cohesion when breaking up classes requires a simple realisation.
- If you notice when you make a smaller class that it isn't that cohesive, say you have 4 instance variables that are only shared between 3 of the methods when there are say 6 methods in that class.
- Extract the 3 and the instance variables they share into a smaller, more cohesive class.
- Doing this makes our classes even smaller and gives us more cohesion.
Organising for Change
- Change is continual and classes should hence be designed and organised to be able to handle change effectively.
- Private method behavior that applies only to a small subset of a class can be a useful heuristic for spotting potential areas for improvement.
- New features should be implemented by extending the system, not making changes to existing code.
- Factor out public interface methods along with their necessary private methods into its own subclass.
- This results in:
- Classes becoming more simple, comprehensible and testable
- Reduced risk of breaking code since modifications are made in a subclass
- Supports SRP
- Supports the Open-Close Principle (OCP), the restructured subclass enables new functionality but keeps other classes closed during this process.
Isolating from Change
- We can introduce interfaces and abstract classes to help isolate the impact of change on our code.
- Dependencies upon concrete details make testing difficult because the expected result is volatile.
- This supports the Dependency Inversion Principle by minimising coupling.
- Classes should depend on abstractions, not on concrete details.