6. Introduction to System Design & UML Diagrams

You've been writing classes for weeks. Today we're zooming out. Systems-level thinking means seeing how multiple classes work together to solve a real problem. This isn't just academic—when you interview, you'll be asked to design systems.

By the end of this lesson, you'll be able to answer this question: Consider an application for making a pet adoption system: What classes exist, what are their responsibilities, and what are the interactions between them? Illustrate this system with a UML diagram.

Table of Contents:

Key Concepts

  • System design is the process of understanding the responsibilities of the components of a system and their relationships/interactions to/with one another.

  • Class Systems are classes with relationships and responsibilities working together

  • An entity is a single component of a system, such as a class in a class system.

  • Unified Modeling Language (UML) is an industry-standard format for representing the components of a system, their responsibilities, and their relationships.

  • A multiplicity labels how many of one class relates to another (e.g. "one-to-many" → "One doctor has many appointments")

OOP Review / Practice

Create a Book class:

  • Properties:

    • title: String, public, set by the constructor

    • author: String, public, set by the constructor

    • genre: String, public, set by the constructor

  • Methods: None

Then, create a Library class:

  • Properties:

    • name: String, public, set by the constructor

    • books: Book[], private, starting value of []

  • Methods:

    • getBooks(): Returns a copy of the private books array

    • addBook(book): Adds the given book to the private books array. Bonus if you can add a guard clause that ensures that all books added to the library come from the Book class.

Example Usage:

const myBook = new Book("Beloved", "Toni Morrison", "Historical Fiction");
console.log(myBook); // Book { title: "Beloved", author: "Toni Morrison", genre: "Historical Fiction"}

const myLibrary = new Library("Brooklyn Central");
console.log(myLibrary); // Library { name: "Brooklyn Central"}

// Adding a book
myLibrary.addBook(myBook);
console.log(myLibrary.getBooks()); // [ Book { title: "Beloved", author: "Toni Morrison", genre: "Historical Fiction"} ]

// Only allow Book instances to be added. These shouldn't be added!
myLibrary.addBook("not a book");
myLibrary.addBook({ description: "I'm a book!" });
console.log(myLibrary.getBooks()); // [ Book { title: "Beloved", author: "Toni Morrison", genre: "Historical Fiction"} ]
Solution
class Book {
  constructor(title, author, genre) {
    this.title = title;
    this.author = author;
    this.genre = genre;
  }
}

class Library {
  #books = [];
  constructor(name) {
    this.name = name;
  }
  addBook(book) {
    if (!(book instanceof Book)) {
      console.log("book must be a valid Book instance")
      return;
    }
    this.#books.push(book);
  }
  getBooks() {
    return [...this.#books]
  }
}

Case Study: Pet Adoption Center

The Pet Adoption Center wants your help designing a system.

Imagine that a nation-wide Pet Adoption Center approaches you to build an application for them. The application needs to handle the following:

  • Each shelter location needs a way to accept new Pets to be put up for adoption and keep track of them.

  • Adopters need some way to apply for pet adoption.

  • The shelter needs a way to review applications and approve or reject them.

Pause and Think: How would you go about building an application to handle these requirements? Where would you start?

System Design

System design is the process of understanding (1) the components of a system, (2) the responsibilities of those components, and (3) their relationships to/interactions with each other.

System design is a very broad term but in the context of Object-Oriented Programming (OOP), we can narrow its definition down a bit:

  • Components: The classes in your application (the "things").

    • Example: a Library and Book

  • Responsibilities: The properties and methods that each class "owns"

    • Example: A Library has #books and methods addBook, getBooks

  • Relationships: Inheritance or references between classes

    • Example: a Library references Books, or perhaps a Biography extends a Book

Discussion Question: What are the components, responsibilities (properties/methods), and relationships for the Pet Adoption Center application?

Components could include shelters, owners, pets, adopters/applicants, applications, release forms, etc...

Responsibilities for an applicant could include properties like their name, contact information, a list of their applications that they've submitted, a list of any pets that they own. Methods could be submitting an application or getting their pets or getting their pending applications.

Relationships for an adopter could include references to the pets they own and applications they've submitted.

This is just scratching the surface — there is certainly a lot more that goes into this system!

UML Diagrams & Iterative Design

Designing a system well can be really hard. Often times, many iterations of "wrong" solutions must be thrown away before the "right" solution emerges. To provide structure to this iterative design process, engineers turn to UML Diagrams.

Unified Modeling Language (UML) and UML Diagrams are the industry-standard format for visualizing:

  1. the components/classes of a system (a.k.a. "entities")

  2. the responsibilities of each entity (properties and methods)

  3. the relationships between entities

A UML diagram for the Pet Shelter might look like this:

A UML Diagram shows the responsibilities and relationships of classes in a system.

Take a few minutes to scan over the diagram.

Q: Without looking too closely, what are the visual components that show the entities in the system? What responsibilities and relationships do you see?
  • Boxes represent classes with properties and methods inside

  • Arrows show relationships with labels and numbers

This system looks pretty complex, and it is! But it didn't get created in an hour — it took many iterations to get to this somewhat final form.

UML Diagrams provide engineers a way to iterate on and communicate about a system's design without committing any time or resources to implementing it in code.

UML Diagrams are like a draft of a system before it is implemented in code.

Q: Why is it useful to create a UML diagram as a "first draft" of a system before implementing the system in code?

UML diagrams allow engineers to think through the design and iterate on it until they are absolutely certain about what they are building. It would be a massive waste of time and resources to build a system only to have to rebuild it due to a poor design.

In a team setting, a UML diagram helps engineers align on the system. Each engineer can focus on one component knowing precisely how their component will interact with others.

With generative AI, a well-designed system document can be handed off and built in minutes!

UML Syntax: Class Responsibilities

In a UML diagram, each box represents a single entity (class) with its name, properties, and methods inside:

A single entity (class) is drawn as a box with (1) the name of the class, (2) the properties of the class, (3) the methods of the class
  • A + indicates a public property (e.g. + name: String)

  • A - indicates a private property (e.g. - isAvailable: Boolean)

  • Each property is listed with their data type.

  • Methods include parameter names and the returned type.

Practice: On your whiteboard or in your journal, draw a UML diagram for the Book class and the Libary class:

class Book {
  constructor(title, author, genre) {
    this.title = title;
    this.author = author;
    this.genre = genre;
  }
}

class Library {
  #books = [];
  constructor(name) {
    this.name = name;
  }
  addBook(book) {
    if (!(book instanceof Book)) {
      console.log("book must be a valid Book instance")
      return;
    }
    this.#books.push(book);
  }
  getBooks() {
    return [...this.#books]
  }
}
Solution

Here is a text-based UML Diagram for Book and Library

┌───────────────────┐
│       Book        │
├───────────────────┤
│ + title: string   │
│ + author: string  │
│ + genre: number   │
├───────────────────┤
│                   │
└───────────────────┘

┌───────────────────────┐
│        Library        │
├───────────────────────┤
│ + name: string        │
│ - books: Book[]       │
├───────────────────────┤
| + addBook(book): void |
| + getBooks(): Book[]  |
└───────────────────────┘

UML Syntax: Relationships

To illustrate relationships between classes in a system, we use a combination of arrows, labels, and multiplicity notation:

Arrows, labels, and multiplicity notation show the relationships between classes.

Arrows: Uni-Directional vs. Bi-Directional

  • Arrows indicate that one class reference another. References can either be uni-directional (one-way) or bi-directional (two-way)

Q: For each relationship, identify which are uni-directional and which are bi-directional
  • Shelter --> Pet (one-way)

  • Shelter --> Application (one-way)

  • Application --> Pet (one-way)

  • Application <--> Adopter (two-way)

Multiplicity

The numbers at each end of the arrow is called multiplicity notation. It indicates how many instances of one class can be associated with instances of another class:

For example, each shelter holds references to 0 or more pets which can be notated like: Shelter 1 --> 0..* Pet

More examples of multiplicity notation include:

  • Exactly one: 1

  • Zero or more: 0..*

  • One or more: 1..*

Q: Can an Adopter submit zero applications?

Yes

Q: Can an Application exist without an Adopter?

No

Q: Can one Pet have multiple applications?

Yes

Q: Can an Application be for multiple pets?

No

Label

The labels are a nice touch to include in your UML diagram.

Practice

Practice: Add an arrow to your UML diagram to indicate the relationship between your Library and Book class. Include a label and multiplicity notation.

System Design Analysis

Now that we understand how to create a UML diagram, how do we decide what goes into it?

Let's analyze the Pet Adoption Center system design and see if we can learn from it. Remember, the Pet Adoption Center requested that the system include the following:

  • The shelter needs a way to accept new Pets to be put up for adoption.

  • Adopters need some way to apply for pet adoption.

  • The shelter needs a way to review applications and approve or reject them.

A UML Diagram shows the responsibilities and relationships of classes in a system.

Analyze the UML diagram for the pet adoption system and, for each feature, identify:

  • Are any new class instances created? If so, with what properties?

  • Which properties are used from each class to make the feature work?

  • Which methods are invoked, and in what order, to make the feature work?

Answer

The shelter needs a way to accept new Pets to be put up for adoption:

  • When a new shelter registers on the application, they create a new shelter instance with a name. The #pets and #applications properties are each set to empty arrays by default.

  • When a pet owner decides to surrender a pet, they create a new pet instance with a name, species, and breed. The #isAvailable property is set to false by default.

  • They invoke shelter.addPet(pet) to transfer the pet to shelter management.

  • The shelter sets the pet's availability to true upon intake via pet.setAvailability(true).

Adopters need some way to apply for pet adoption:

  • An adopter registers for an account, creating a new adopter instance with name and email. The #applications property is set to an empty array by default.

  • The adopter invokes shelter.getAvailablePets() to see which pets are available for adoption.

  • The adopter creates a new application instance with adopter.createApplication(pet). The #applicationStatus property is set to "pending" by default.

  • When the adopter is ready to submit the application, they send it to the shelter via shelter.receiveApplication(application) which can validate if the pet in the application (application.getPet()) is held by that shelter.

The shelter needs a way to review applications and approve or reject them:

  • Shelter staff can invoke shelter.getPendingApplications() to see all applications with a "pending" status (application.getStatus()).

  • For each application, invoke either:

    • application.approve():

      • Sets its status to "approved"

      • The shelter sets the pet's availability to false via application.getPet().setAvailability(false)

      • Automatically rejects other applications for the pet via shelter.rejectApplicationsForPet(this.pet)

    • application.reject():

      • Sets its status to "rejected"

      • The pet remains available.

Last updated