Heuristics for Design
- Use real-world objects
- Form abstractions
- Encapsulate details
- Use inheritance to simplify the design
- Information hiding
- Identify where change is likely
- Use strong cohesion
- Build hierarchies
- Formalize class contracts
- Avoid failure
- Brute force when needed
- Draw diagrams
Use Real World Objects
- Identify real world objects to build the system.
- Clients, employees, purchase, bills, etc.
- This will make the system fit the environment of which it is a part.
- Some systems will not have real world counterparts.
- Operating systems have no existence outside the computer.
- Identify attributes of the objects (data and structures).
- Name, address, id#, cost, etc.
- Determine what can be done to each object, what variables can change.
- Determine how objects can interact.
- How can objects be combined (ie. aggregation or inheritance).
- Determine what is visible to other objects.
- What are the public and private parts for both data and methods.
- Define the interfaces (data and methods) which allow access to the public parts (or to the protected parts when using inheritance).
- Decide what to show the user of the class and what to hide inside it.
- Hide details the user doesn’t need to know; ie.
- A good abstraction lets you focus on the interface without needing to know how the class operates.
- Lets you ignore irrelevant details.
- Abstraction can be used at the function/method level, or the module/class level.
- Similar to abstraction but with the added restriction that the developer cannot look into or modify the hidden parts of the class.
- Forces separation.
- Prevents developers from changing encapsulated parts of a class.
- Stops them from doing what they shouldn’t do anyway.
- Use it when the language gives you the facility; however, the same thinking applies to languages that don’t.
Use Inheritance To Simplify The Design
- When objects are similar and have overlapping data or methods use inheritance to describe the common part and create subclasses which describe the differences.
- Employee class example:
- Base class: name, address, age
- Subclasses: employee type: full-time/part-time
- Operations which aren’t specific to one type of employee are handled by the superclass.
- Operations which are specific to the type of employee are handled by the subclass (e.g. pension, vacation days)
- Simplifies design because you don’t need to repeat code in different but similar classes.
- Makes it less likely the code will get out of synch § Does add to complexity of the code.
- If used improperly, inheritance can create some very complicated code.
- This is where it can be handy to think of these concepts implemented in procedural languages (less prone to OO “cleverness” in the solution).
- There is some evidence that aggregation is more useful than inheritance (consider procedural interpretation).
- Instead of creating subclasses, create separate objects and instantiate the common object inside a more specialized object.
- Superclass becomes part of the contents of what would be the subclass.
- Hide complexity
- Limit the effect of changes to the class so they aren’t seen outside of the class (localize changes to the class)
- Large programs which use information hiding are four times easier to modify than those that don’t.
- Similar to encapsulation and abstraction.
- Details of a class are hidden to the user of the class § Reveal as little as possible about the inner workings.
Identify Where Change is Likely
- Identify items which are likely to change (list in requirements).
- Separate items likely to change (put in their own class or classes).
- Isolate them by designing interfaces to hide changes; limit scope of change to inside that class.
- Changing rules of business.
- New laws.
- Dependency on hardware issues.
- Upgrades, or purchase of new hardware.
- New language: porting the code base.
- Accessibility issues (vision, hearing, etc).
- Non-standard extension to the language.
- May need to be re-written.
- Minimize the effect of poor design.
- Indicating state of program.
- Can change a lot, especially during development.
- Maximum size changes over time.
- New employees are hired, more disk space consumed.
- Sizes which were appropriate to begin with become too small over time.
- All routines and data in a class/module should support a single, well-defined purpose.
- ie. don’t have a database class that also deals with files or error messages.
- Files might sound logical, but the class will become confusing when they are mixed together.
- Used to manage complexity.
- A single class with a single purpose is easier to understand and remember.
- Recall coupling/cohesion are related concepts:
- Software components should not be too tightly bound to other classes
- This applies at all levels: system, module, class, function, etc.
- Organize from the general (top) to the specific (bottom).
- This divides complexity into levels.
Formalize Class Contracts
- Think of the interface of a class/module as a contract.
- If it is given the correct input, then it will create the correct output.
- Require preconditions to generate postconditions.
- This means that objects can ignore inputs that don’t follow the contract.
- If it is well defined then you can test the input and ignore anything that breaks the contract.
- Note: pre/post conditions are not a means of doing no verification & validation.
Design for Testing
- Make modules easy to test.
- Easy to plug testing code into each module individually.
- This encourages subsystems which are not overly dependent on each other (loose coupling).
- Don’t focus solely on things that worked previously.
- Look at what could fail.
Brute Force When Needed
- A brute force method that works is better than an elegant solution that doesn’t.
- Elegant solutions can take a long time to make work.
- ie. Binary search is elegant but prone to error in spite of its simplicity– is sequential search sufficient for this case?
- Understanding is critical.
- Consider hash table behaviour.
- Pictures simplify the design.
- Abstract away unnecessary details.
- Make it easier to understand.