Module 2: Class Design

Module Overview

Learn about principles of good class design and object-oriented programming.

Learning Objectives

  • Apply encapsulation by using private instance variables and public methods
  • Implement getter and setter methods to provide controlled access to instance variables
  • Create JavaDoc comments to document class behavior
  • Implement the ToString method to create a string representation of an object
  • Create well-designed classes that balance cohesion and coupling
  • Design classes that follow the single responsibility principle
  • Implement validation logic in methods to maintain object consistency

Key Concepts Explained

Encapsulation

Encapsulation is one of the four pillars of OOP. It involves bundling data (attributes) and methods that operate on that data within a single unit (a class), and restricting direct access to some of the object's components.

public class Student {
    // Private instance variables - encapsulated
    private String name;
    private double gpa;
    
    // Public methods to access and modify the encapsulated data
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public double getGpa() {
        return gpa;
    }
    
    public void setGpa(double gpa) {
        // Validation to maintain consistency
        if (gpa >= 0.0 && gpa <= 4.0) {
            this.gpa = gpa;
        } else {
            throw new IllegalArgumentException("GPA must be between 0.0 and 4.0");
        }
    }
}

Single Responsibility Principle

A class should have only one reason to change, meaning it should have only one responsibility or job.

// Good: Each class has a single responsibility
public class Order {
    private List items;
    private Customer customer;
    
    // Order-related methods
}

public class OrderProcessor {
    public void processOrder(Order order) {
        // Logic for processing orders
    }
}

public class OrderPrinter {
    public void printOrder(Order order) {
        // Logic for printing orders
    }
}

JavaDoc Comments

JavaDoc is a documentation generator that extracts comments from Java source code and generates formatted HTML documentation.

/**
 * Represents a bank account with basic operations like deposit and withdraw.
 * 
 * @author Jane Doe
 * @version 1.0
 */
public class BankAccount {
    private double balance;
    
    /**
     * Deposits money into the account.
     * 
     * @param amount The amount to deposit
     * @return The new balance after deposit
     * @throws IllegalArgumentException if amount is negative
     */
    public double deposit(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("Cannot deposit a negative amount");
        }
        balance += amount;
        return balance;
    }
}

ToString Method

The toString() method returns a string representation of an object and is automatically called when an object is printed.

public class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

Design Principles

Balancing Cohesion and Coupling

Good class design aims for high cohesion (related functionality in one place) and low coupling (minimal dependencies between classes).

High Cohesion

A class should represent a single, well-defined concept with related methods and attributes.

// High cohesion - all methods relate to customer management
public class CustomerManager {
    public void addCustomer(Customer customer) { /* ... */ }
    public void removeCustomer(String customerId) { /* ... */ }
    public Customer findCustomer(String customerId) { /* ... */ }
    public void updateCustomerDetails(Customer customer) { /* ... */ }
}

Low Coupling

Classes should have minimal knowledge of or dependencies on other classes.

// Low coupling example using interfaces
public interface PaymentProcessor {
    boolean processPayment(Payment payment);
}

public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public boolean processPayment(Payment payment) {
        // Credit card specific implementation
    }
}

public class Order {
    private PaymentProcessor paymentProcessor;
    
    // Constructor injection creates loose coupling
    public Order(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }
    
    public void checkout(Payment payment) {
        // Order doesn't need to know which payment processor is used
        boolean success = paymentProcessor.processPayment(payment);
        // Process result
    }
}

Validation in Methods

Methods should validate inputs to ensure object consistency and prevent invalid states.

public class Product {
    private String name;
    private double price;
    
    public void setName(String name) {
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("Product name cannot be empty");
        }
        this.name = name;
    }
    
    public void setPrice(double price) {
        if (price < 0) {
            throw new IllegalArgumentException("Price cannot be negative");
        }
        this.price = price;
    }
}

Additional Resources

Practice Exercises

Exercise 1: Library Management System

Design a set of classes for a library management system:

  • Create a Book class with proper encapsulation and validation
  • Create a Library class that manages a collection of books
  • Implement methods for adding, removing, and finding books
  • Document all classes and methods with JavaDoc
  • Implement toString() methods for all classes

Exercise 2: E-commerce System

Design a simple e-commerce system with the following classes:

  • Product class with proper validation
  • ShoppingCart class to manage product collections
  • Customer class with contact information
  • Order class that links customers, carts, and payment information
  • Ensure each class has a single responsibility

Guided Projects

Complete these exercises to reinforce your understanding of classes, objects, and access modifiers.

Mastery Task 5 - UML Diagraming

Mastery Task Guidelines

Mastery Tasks are opportunities to test your knowledge and understanding through code. When a mastery task is shown in a module, it means that we've covered all the concepts that you need to complete that task. You will want to make sure you finish them by the end of the week to stay on track and complete the unit.

Each mastery task must pass 100% of the automated tests and code styling checks to pass each unit. Your code must be your own. If you have any questions, feel free to reach out for support.

AppSettings Update

Go to com.adventure.settings.AppSettings and update the story to MT5_DigEscape.

UML

You will be implementing the following UML diagram: UML Diagram Link

Working Classes

We will be working with the following classes:

  • Hole - adventure.world.objects.Hole
  • HoleContent (new)

Turning UML into Code

The first task is to create the HoleContent object. You should be able to use the UML diagram to guide you in creating the class.

Note: the HoleContent file does not exist yet. You'll need to create it from scratch.

Using HoleContent in Hole

Once HoleContent is created, it's time to use it in the Hole object.

The Hole constructor receives a Key which should be used in creating a HoleContent object.

Inside of the Hole class, you should update each of its functions so that it uses HoleContent to set and return the correct values.

Testing

To run the tests for this assignment, run the following:

./gradlew test --tests com.adventure.MT5

or by right-clicking the MT5 file and selecting "Run 'MT5'"

You can check your code styling by running the linter with the following command:

./gradlew checkstyleMain

You can run the game by going to the Main class and clicking on the run icon to the left of the main() method.