SENG350 - OO Principles

Neil Ernst

2025-09-24

Overview/Learning Objectives

  • understand the importance of design
  • introduce basic principles in design

A good design principle should help generate ideas and enable you to think through design implications. – Rebecca Wirfs-Brock

Modularity and modifiability

  • Nearly every system has a modifiability QAS.
  • Modularity is a design approach to make modifiability possible.
  • We’ve known for decades that systems must constantly be maintained to stay up to date
    • library upgrades, security holes, new features, removing dead code, etc.
  • Each system will have its own response measure (e.g., deploy to PROD in 2 days)

Design for Modularity

  • Decomposable - can be broken down into modules to reduce complexity and allow teamwork
  • Composable - “Having divided to conquer, we must reunite to rule [M. Jackson].”
  • Understandable - one module can be examined, reasoned about, developed, etc. in isolation
  • Continuity - a small change in the requirements should affect a small number of modules
  • Isolation - an error in one module should be as contained as possible

taken from UW CSE331 - Hal Perkins

Key Principles - Overview

  1. Cohesion/Coupling
  2. SOLID
    1. Single Responsibility
    2. Open/Closed
    3. Liskov Substitution
    4. Interface Segregation
    5. Dependency Inversion/Inversion of Control
  3. Refactoring

Cohesion and Coupling

  • Cohesion is internal to an object, describing its independence. High cohesion implies doing one thing well.
    • In particular, the object/module has only one reason to change. What are some examples?
  • Coupling is the amount of dependency between components. High coupling makes the preceding modularity approaches harder. (Why?)
  • Modular systems have objects with high cohesion and low coupling

Class-Responsibility-Collaborator (CRC) Diagrams

  • CRC diagrams - Class Responsibility Collaborator
  • CRC diagram -> “A responsibility is anything that a class knows or does”
  • a class should have a single purpose, collecting together a set of related sub-responsibilities
  • an index card with name, responsibilities, collaborators (dependencies).

Exercise

Let’s do some modeling. Create a skeleton for a simple Soccer league management system. This system tracks teams playing against one another, collecting points for victories, and ensuring equal playing schedules.

  1. Create classes for the different objects you think are needed.
  2. Create one class with many responsibilities
  3. Create one class with many collaborators

Refactoring

  1. Refactoring foreshadowing: pick one of the previous two classes, and make at least 2 classes out of it.

God Class

  • the class that does it all
  • common when moving from imperative languages or scripting
  • low cohesion - does many things, not connected
  • low coupling externally but now high coupling internally
  • look for classes with many many methods.

Naming

  • Surprisingly, one of the most important tasks developers have
  • Rely on naming conventions (yours, project, language, corporate)
  • Name classes to describe effects and purpose, not implementation

Naming

For example, in the HotDraw drawing framework, my first name for an object in a drawing was DrawingObject. Ward Cunningham came along with the typography metaphor: a drawing is like a printed, laid-out page. Graphical items on a page are figures, so the class became Figure. In the context of the metaphor, Figure is simultaneously shorter, richer, and more precise than DrawingObject.”

If a class is hard to name, it is probably doing too much.

Naming

Rate of change:

Things that change at the same rate belong together. Things that change at different rates belong apart. This principle is true of both the data manipulated by the program–two variables that always change at the same time belong in the same object–and the structure of the program–two functions that change at the same time belong in the same class.

“Empathy in naming” -> “if I searched for this function, what would I want it called?”

source: https://www.facebook.com/notes/kent-beck/naming-from-the-outside-in/464270190272517/

–>

Back to Key Principles - Overview

  1. Cohesion/Coupling
  2. SOLID
    1. Single Responsibility
    2. Open/Closed
    3. Liskov Substitution
    4. Interface Segregation
    5. Dependency Inversion/Inversion of Control
  3. Refactoring

Single Responsibility

  • “class only has a single reason to change”
  • a Rectangle that calculates area and draws itself
  • what two types of responsibility should almost certainly be separated?

Open Closed Principle

“Objects are open for extension but closed for modification.”

  • use inheritance and abstraction

Ask: if a new object of type X is needed, where do you make changes?

  • minimize changes to existing objects
  • contrast: lengthy switch statements (or using instanceof)

OCP cont.

Encapsulation: keep data and operations together.

  • Ensure internal details don’t have external effects (eg. member variables)
  • Does inheritance violate this?

Liskov Substitution Principle (LSP)

“Functions that use Base Classes must be able to use Derived Classes without being aware of it”

  • a change to a derived class (subtype) shouldn’t affect the program using the base class (OCP)
  • The validity of a model can only be expressed in terms of its clients (tests).

Interface Segregation

“Clients should not depend on interfaces they don’t use”

  • do not create coupling between objects through implicit dependencies on a common interface (rich interface)

Dependency Injection/Inversion of Control

A. High Level Modules Should Not Depend Upon Low Level Modules. Both Should Depend Upon Abstractions.

B. Abstractions Should Not Depend Upon Details. Details Should Depend Upon Abstractions.”

Separate configuration from use, especially in the use of 3rd party libraries.

DI Frameworks

Frameworks like Guice, Inversify allow for “injection” of the dependencies

  • e.g., at runtime, allow new instances of Report
  • especially useful for testing (code no longer cares if it gets a mock or the real object)

public class RealBillingService implements BillingService {
  
  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
  
    CreditCardProcessor processor = new PaypalCreditCardProcessor();
  
    TransactionLog transactionLog = new DatabaseTransactionLog();
  }
}

DI - 2


public class BillingModule extends AbstractModule {
@Override 
protected void configure() {
    bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
   // more bindings
}
...
public class RealBillingService implements BillingService {
    @Inject
  public RealBillingService(CreditCardProcessor processor, TransactionLog transactionLog) {
     this.processor = processor;
  this.transactionLog = transactionLog;
}

Summary

Design is about the big picture, but also the series of little steps we take as we implement.

Modularity means things are easier to change, and software is always changing.

Design principles give us some guidelines on how to approach implementation tradeoffs.