1 of 28

Team 302 Software Training

Design Principles

2 of 28

Design Principles

Prerequisite Training:

  • None

3 of 28

4 of 28

Design Principles

  • DRY vs. WET Code
  • Interfaces vs. Implementation
  • Composition over Inheritance
  • Delegation
  • SOLID Design Techniques
    • Single Responsibility
    • Open/Closed Principle
    • Liskov Substitution Principle
    • Interface Segregation
    • Dependency Inversion Principle

5 of 28

DRY vs. WET Code

  • DRY vs. WET
    • DRY: Don’t Repeat Yourself (reuse code)
    • WET:Write Everything Twice

  • We want DRY code … why?
    • Less memory consumption
    • Bug fixing time can be reduced
    • Resulting code is likely to be more robust
    • Performance/memory improvements can be leveraged multiple times
    • Unit testing time can be reduced

6 of 28

Interface vs. Implementation

  • Interface
    • Defined public behavior
    • Implemented with an Abstract Virtual Class (all methods are Virtual and = 0;) with a default constructor and destructor
  • Program to the published interface, not the the details of the implementation
    • Unaware of the actual object type (e.g. don’t Cast)
    • Unaware of the details in the implementation, so if -- when -- they change this other classes are unaffected
      • A drawing application has Shapes (squares, circles and triangles)
      • The ShapeManager the holds onto all of the shapes
      • The Area function shouldn’t write its implementation assuming:
        • All of the squares are returned first, then circles and triangles
        • Even though this is true, why not?

7 of 28

Inheritance

  • Object hierarchy is based on what the object is
  • Objects have “is a” relationship (e.g. a Golden Retriever is a Dog)
  • Promotes code reuse
    • Base class can implement functionality that the subclass can just inherit (use); no need to write twice
  • Ideally the hierarchy should be shallow because there is a tight coupling between Base Classes and Subclasses.
    • Subclasses are exposed to the implementation of the base class (parent)
  • One directional linkage (just because a Golden Retriever is a Dog does not mean that a Dog is a Golden Retriever)
  • Branches of the hierarchy cannot cross/inherit
    • Golden Retriever & Irish Setter both inherit from Dog, not from each other

8 of 28

Inheritance

  • Common behaviors can be implemented in the Dog class
    • Bark
    • Run
    • Wag Tail
  • Specific behaviors can be implemented in the subclass
    • Fur Color
    • Fur Type

Dog

Poodle

Great Dane

Irish Setter

Golden Retriever

9 of 28

Composition

  • Object hierarchy is based on what the object does
  • Simplifies things (each class/object does one thing really well)
  • Objects have a “has a” relationship
    • Computer “has a” CPU
    • Computer “has a” Graphics Card
    • Computer “has a” SSD
    • Computer “has a” RAM
    • Car “has a” engine, etc.
  • New functionality to be added without having to change unrelated classes
  • Behavior can be varied at runtime

10 of 28

Composition

  • Different Chassis/mechanisms can be swapped in
  • Example, when we added the active grabber we just swapped in a new class without impacting other classes

Original Robot

Passive Grabber

Elevator

Omni Chassis

Final Robot

ActiveGrabber

4Bar

11 of 28

Composition - Changing behavior at runtime

  • Each state of a mechanism could be a separate class and we just manage what is the “active” state
  • Different classes for calculating the robot pose (position/orientation) based on what sensors are available.
    • Lose a sensor (encoder, gyro, etc.), switch which sensors are being used for the calculations
  • Object detection code may switch what sensors are involved based on the object being detected
    • Is it a retroreflective tape target?
    • Is it a game piece? Which one?
    • Is it another robot or a field element?

12 of 28

Favor Composition over Inheritance

  • Composition encourages loose coupling (change one thing independent of other things)
  • Rock solid concepts that don’t need to change as the system changes
    • Encapsulate behaviors into classes ⇒ Rock solid concepts don’t change as functionality is added.
    • These behaviors (both common and different) can be composed into other classes
    • Use interfaces for the hierarchy
    • Behavior can change whenever needed including at runtime
  • Inheritance promotes large hierarchies
    • Some inherited methods may not make sense in some classes
    • Keep inheritance “shallow” (not too many levels)
  • Tends to have the most value as changes are made
    • Unanticipated Changes (common)
    • Behavior Changes (common)

13 of 28

Example - Inheritance

  • Assume you are creating a game based on the Buddy Movies
  • There is a Golden Retriever class that can Run, Eat, Sleep and Bark. Since it is a dog and you plan on adding different types of dogs (e.g. the police dog) that might have different characteristics (e.g. fur color). You create a Dog class with these capabilities and the Golden Retriever class inherits from Dog
  • A Siamese Cat gets added it runs, eats and sleeps, but doesn’t bark. So, you add an animal class that has the eat and sleep functionality. Dog has bark and Cat has meow
  • The cat is going to go fishing, so you add a Fish which eats and sleeps but doesn’t run. So you create an animal class that has eats and sleeps. Dog and Cat now each have a run method (so you lost re-use on this).
  • Another option would be refactor run to move and let the fish implement it as swimming.

14 of 28

Example - Composition

  • Assume you are creating a game based on the Buddy Movies
  • Create an Dog interface which has Run, Eat, Sleep and Bark methods.
  • The Golden Retriever Class implements the Dog interface.
  • It is composed of:
    • Sleep Class
    • Run Class
    • Bark Class
    • Eat Class
  • When the Siamese Cat gets added, it implements the Cat interface which has Run, Sleep and Eat.
  • It gets composed of:
    • The same Sleep, Run and Eat class
    • A new Meow class.
  • May desire to have an Animal interface that has
    • Sleep, Run, Speak and Eat behaviors

15 of 28

Delegation

  • Don’t do everything yourself
  • Use Composition
    • Smaller classes
    • Helps Re-use
  • Previous example behaviors were delegated to other classes

16 of 28

SOLID Design Techniques

  • Single Responsibility Principle - one reason to change class
  • Open/Closed Principle
    • Open for Extension, Closed to modification
    • Code to Abstractions
  • Liskov Substitution Principle
    • Design by Contract
  • Interface Segregation
    • Client specific interfaces are better than general purpose ones
  • Dependency Inversion Principle
    • Depend on Abstractions not concrete implementations

17 of 28

Single Responsibility Principle

A class should do only one thing (AND DO IT WELL!!)

  • One reason to change class
  • Separate Concepts
  • Should be easy to document the class without using conditional terms
  • A Swiss Amry knife or multi-purpose tools are great, but …
    • Do they really have the best screwdriver?
    • A toolbox full of the best tools might work better
    • Think of each tool as a class.
  • KISS (Keep It Super Simple) Principle

18 of 28

Single Responsibility Principle

Separate classes to:

  • Read Joystick inputs
  • Set Chassis Motors

As opposed to one class that reads the Joystick inputs and sets the Chassis Motor Speeds

19 of 28

Open/Closed Principle

Software should be open for extensions but closed for modifications

Image courtesy of Derick Bailey.

20 of 28

Open/Closed Principle

  • Don’t change classes to add behavior
  • Add another class to extend the existing behavior (subclass or composition)

Example of how we could have done it ….

  • The passive grabber we started with could have been:
    • “Decorated” this grabber by adding another class that would control the wheels
    • Subclassed to have wheeled behavior control
  • The passive grabber class wouldn’t be modified

21 of 28

Liskov Principle

Design by contract, so that a subclass can replace a base class without causing a problem.

Image courtesy of Derick Bailey.

22 of 28

Liskov Principle

  • Subclasses shouldn’t be empty or throw errors
  • Example
    • You are writing a game that has birds in it that fly around, so you create:
      • Bird class that has SetAltitude and SetSpeed, so you know how high and how fast the bird is flying.
      • Hawk, Eagle and Robin are subclasses of Bird and everything works fine
      • Then you have to add a new world and end up at the South Pole, so you have to add Penguins
        • Things to don’t work so well because Penguins don’t fly so what happens if the altitude is set to 10 meters?

23 of 28

Liskov Principle

  • Application calculates area and perimeter of a polygon shapes
  • Rectangles have methods to update their dimensions using the SetLength and SetWidth methods
  • Squares get added (they are just special rectangles, so you subclass Rectangle)
  • Client now calls SetLength and SetWidth on the square
    • Setting the length also changes the width and vice versa
    • Calculated Area/Perimeter may not be what was expected

24 of 28

Interface Segregation

No client should be force to depend on a method it doesn’t use

Image courtesy of Derick Bailey.

25 of 28

Interface Segregation

  • Use small, specific interfaces
  • If interfaces become large break them up
    • That is the beauty of C++ is it support multiple inheritance

Imagine an application that calculates the area and perimeter of polygon shapes. The interface IShape has methods CalcArea() and CalcPerimeter(). Later, it is decided that the application draw the shapes as well as calculate these values. Should you just add a Draw() method on the IShape() interface? Will all clients care about it? Will there be other methods for drawing later?

26 of 28

Dependency Inversion

High level modules shouldn’t depend on low level modules. Both should depend on abstractions.

Similar to electrical create plugs and sockets (interfaces) to hook things together.

Image courtesy of Derick Bailey.

27 of 28

Dependency Inversion

  • Decouple classes ⇒ Use interfaces
  • Logic interacts with the interface not the concrete implementations
  • This allows a concrete implementation to be added that uses the same interface to work without changing everything the deals with it.

28 of 28

Dependency Inversion

  • Shooter angle is determined by a potentiometer
  • Instead of coding to use the potentiometer
    • Create an interface such as AngleSensor which has SetAngle() and GetAngle()
    • Now if the potentiometer gets swapped out for an encoder the rest of the system doesn’t understand this
    • This allows us to swap different sensors if there are mounting issues or at a competition the sensor fails and we don’t have a backup.