Index

Case Study 3 Task Manager App with JavaFX

Java Object-Oriented Design

21.1 MVC in Object-Oriented GUI Design

Designing graphical user interfaces (GUIs) can quickly become complex as applications grow in functionality. To manage this complexity and produce maintainable, testable, and scalable applications, software architects often rely on design patterns. One of the most influential and widely adopted patterns for GUI design is Model-View-Controller (MVC).

Understanding the MVC Pattern

MVC is a separation of concerns architectural pattern that divides an application into three interconnected components:

This separation isolates concerns, allowing developers to work on UI design, business logic, and input handling independently. It also supports parallel development, easier testing, and more flexible maintenance.

MVC in the Task Manager App Context

Consider building a Task Manager app using JavaFX—a popular Java framework for rich client applications. The app allows users to create, edit, delete, and mark tasks as complete. Mapping this functionality into the MVC components helps keep the design clean and manageable.

Model: Task Data and Business Logic

The Model contains the classes representing tasks and their states. For example, a Task class encapsulates properties like:

The Model manages how tasks are stored, validated, and modified. It might also include business rules, such as preventing a task’s due date from being set in the past.

The Model is independent of the UI. It can notify observers about state changes (e.g., using JavaFX’s ObservableList or property bindings), allowing the View to update automatically.

View: User Interface Components

The View is responsible for displaying the task list, task details, and controls (buttons, text fields). In JavaFX, this typically involves FXML files or programmatically constructed UI nodes:

The View binds to the Model’s data using JavaFX’s property bindings, providing a responsive interface that reflects the current state.

Controller: Event Handling and Interaction Logic

The Controller processes user interactions such as button clicks or list selections. It reads input from the View, validates it if necessary, updates the Model, and triggers UI updates.

For example:

The Controller acts as a bridge that interprets user actions and applies them to the application state.

MVC Structure Diagram

To visualize the flow and relationships, consider this simplified UML-like diagram illustrating MVC for the Task Manager app:

+----------------+          updates          +----------------+
|      View      | ----------------------->  |     Model      |
|  (JavaFX UI)   | <-----------------------  | (Task Data)    |
+----------------+         notifications     +----------------+
         ^                                   ^
         | handles input                     | notifies changes
         |                                   |
         |                                   |
+----------------+                           |
|   Controller   | --------------------------+
| (Event Logic)  |
+----------------+

JavaFX Example Demonstrating MVC Separation

Below is a simplified code snippet illustrating MVC separation in JavaFX for adding a task:

Model (Task.java)

public class Task {
    private final StringProperty title = new SimpleStringProperty();
    private final BooleanProperty completed = new SimpleBooleanProperty(false);

    public Task(String title) {
        this.title.set(title);
    }

    public String getTitle() { return title.get(); }
    public void setTitle(String value) { title.set(value); }
    public StringProperty titleProperty() { return title; }

    public boolean isCompleted() { return completed.get(); }
    public void setCompleted(boolean value) { completed.set(value); }
    public BooleanProperty completedProperty() { return completed; }
}

View + Controller (TaskManagerController.java)

public class TaskManagerController {

    @FXML private TextField taskInputField;
    @FXML private ListView<Task> taskListView;
    private ObservableList<Task> tasks = FXCollections.observableArrayList();

    @FXML
    public void initialize() {
        taskListView.setItems(tasks);
        taskListView.setCellFactory(list -> new ListCell<>() {
            @Override
            protected void updateItem(Task task, boolean empty) {
                super.updateItem(task, empty);
                setText(empty || task == null ? "" : task.getTitle());
            }
        });
    }

    @FXML
    public void handleAddTask() {
        String title = taskInputField.getText();
        if (title != null && !title.trim().isEmpty()) {
            Task newTask = new Task(title.trim());
            tasks.add(newTask);
            taskInputField.clear();
        }
    }
}

Here, the Controller handles input and updates the Model (tasks list), while the View reflects those changes automatically. The use of property bindings and observable lists helps keep the UI in sync without manual refresh logic.

Benefits of MVC in JavaFX GUI Design

Summary

The Model-View-Controller pattern is a cornerstone for building robust, maintainable JavaFX applications such as the Task Manager app. By clearly separating data, UI, and interaction logic, MVC facilitates clean code organization, easier testing, and scalable design. As you continue developing, remember that maintaining this separation helps keep your codebase flexible and responsive to change.

Index

21.2 Layered Architecture

Layered Architecture

In software design, as applications grow in complexity, organizing code into distinct layers becomes critical for maintainability, modularity, and scalability. Layered architecture is a widely adopted approach that separates an application into logical layers, each with clearly defined responsibilities. This separation enables independent development, testing, and modification of each layer while reducing tight coupling between components.

Defining Layered Architecture

Layered architecture divides software into stacked layers, where each layer communicates primarily with the layer directly below or above it. The most common layers include:

By organizing code this way, each layer is responsible for one aspect of the application, adhering to the Single Responsibility Principle. Changes in one layer—such as updating the database schema—minimally impact other layers, making the system more resilient to change.

Layered Architecture in the Task Manager App

Let’s contextualize layered architecture within our Task Manager app built using JavaFX. The app supports creating, updating, and viewing tasks, so structuring it into layers clarifies responsibilities and fosters maintainability.

Presentation Layer (UI)

This layer is where the JavaFX user interface components live—buttons, forms, lists, and other controls that interact directly with the user. The UI is responsible for rendering task data and capturing user input but contains minimal business logic. It delegates commands to the Business Logic Layer and reflects changes made there.

Example components:

Business Logic Layer

The business logic layer processes user requests, applies business rules, and coordinates application workflows. It manipulates task data, validates inputs, enforces constraints (e.g., no overdue tasks without a due date), and provides services for managing tasks.

This layer acts as a bridge between the UI and data layers, encapsulating the core behavior of the application. It does not handle UI rendering or data storage directly.

Example:

Data Access Layer

This layer interacts with the data store, whether it’s a database, file system, or in-memory collection. It handles saving, retrieving, updating, and deleting task data. It abstracts data persistence details so that upper layers remain independent of the storage mechanism.

Example:

Example Diagram of Layered Architecture

+-----------------------+
|   Presentation Layer  |  <-- JavaFX UI (Controller, FXML)
+-----------+-----------+
            |
            v
+-----------------------+
|  Business Logic Layer |  <-- TaskService, Validation, Business Rules
+-----------+-----------+
            |
            v
+-----------------------+
|    Data Access Layer  |  <-- Repository Pattern, Database/File Access
+-----------------------+

The UI calls services in the Business Logic Layer, which in turn calls repository methods in the Data Access Layer. Data flows back up through the layers to the UI for display.

Example Code Snippet Showing Layer Interaction

Business Logic Layer (TaskService.java):

public class TaskService {
    private final TaskRepository repository;

    public TaskService(TaskRepository repository) {
        this.repository = repository;
    }

    public void addTask(Task task) {
        if (task.getTitle() == null || task.getTitle().isEmpty()) {
            throw new IllegalArgumentException("Task title cannot be empty");
        }
        repository.save(task);
    }

    public List<Task> getAllTasks() {
        return repository.findAll();
    }

    // Other business methods...
}

Data Access Layer (InMemoryTaskRepository.java):

public class InMemoryTaskRepository implements TaskRepository {
    private final Map<String, Task> taskStorage = new HashMap<>();

    @Override
    public void save(Task task) {
        taskStorage.put(task.getId(), task);
    }

    @Override
    public List<Task> findAll() {
        return new ArrayList<>(taskStorage.values());
    }

    // Other CRUD methods...
}

Presentation Layer (TaskManagerController.java):

public class TaskManagerController {
    private final TaskService taskService = new TaskService(new InMemoryTaskRepository());

    @FXML
    private TextField taskTitleInput;

    @FXML
    private ListView<Task> taskListView;

    @FXML
    public void handleAddTask() {
        String title = taskTitleInput.getText();
        try {
            Task newTask = new Task(title);
            taskService.addTask(newTask);
            taskListView.getItems().setAll(taskService.getAllTasks());
            taskTitleInput.clear();
        } catch (IllegalArgumentException e) {
            // Show error to user
            System.err.println(e.getMessage());
        }
    }
}

Benefits of Layered Architecture

Supporting Testing and Future Enhancements

With clear layering, you can write unit tests for the business logic using mock implementations of the repository, ensuring your core logic behaves correctly without needing the UI or database. Similarly, UI tests focus only on presentation concerns.

Looking forward, this architecture supports enhancements like:

Summary

Layered architecture is a foundational approach to organizing the Task Manager app into distinct modules, each focusing on a single responsibility. This organization enhances modularity, maintainability, and testability, making it easier to develop, extend, and manage the application over time. By separating the UI, business logic, and data access, developers gain flexibility and resilience, key factors for successful, real-world software systems.

Index

21.3 Integrating User Interface and Business Logic

Integrating User Interface and Business Logic

A key challenge in designing rich desktop applications like a Task Manager is the seamless integration between the user interface (UI) and the business logic. In JavaFX applications, the UI layer handles user interactions and visual presentation, while the business logic layer manages the core operations such as task creation, deletion, and updates. Clean integration between these layers ensures a responsive, maintainable, and scalable app.

This section will illustrate how JavaFX UI components interact with the underlying business logic classes, show runnable examples linking UI events to task operations, and discuss best practices and concurrency considerations.

Linking UI Events to Business Logic

JavaFX provides event-driven programming through UI controls like buttons, lists, and text fields. These components fire events—such as button clicks or list selections—that your controller class listens to and responds by invoking business logic methods.

Here’s a typical flow for integrating UI with business logic:

Example: Adding and Removing Tasks

Suppose we have a simple TaskService that manages tasks, and a JavaFX controller class TaskManagerController managing the UI.

Business Logic Class (TaskService.java):

import java.util.ArrayList;
import java.util.List;

public class TaskService {
    private final List<Task> tasks = new ArrayList<>();

    public void addTask(Task task) {
        tasks.add(task);
    }

    public void removeTask(Task task) {
        tasks.remove(task);
    }

    public List<Task> getAllTasks() {
        return new ArrayList<>(tasks);
    }
}

Model Class (Task.java):

public class Task {
    private String title;

    public Task(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    @Override
    public String toString() {
        return title;
    }
}

JavaFX Controller (TaskManagerController.java):

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;

public class TaskManagerController {
    @FXML
    private TextField taskInput;

    @FXML
    private Button addButton;

    @FXML
    private Button deleteButton;

    @FXML
    private ListView<Task> taskListView;

    private final TaskService taskService = new TaskService();
    private final ObservableList<Task> taskObservableList = FXCollections.observableArrayList();

    @FXML
    public void initialize() {
        taskListView.setItems(taskObservableList);

        addButton.setOnAction(event -> {
            String title = taskInput.getText().trim();
            if (!title.isEmpty()) {
                Task newTask = new Task(title);
                taskService.addTask(newTask);
                refreshTaskList();
                taskInput.clear();
            }
        });

        deleteButton.setOnAction(event -> {
            Task selected = taskListView.getSelectionModel().getSelectedItem();
            if (selected != null) {
                taskService.removeTask(selected);
                refreshTaskList();
            }
        });
    }

    private void refreshTaskList() {
        taskObservableList.setAll(taskService.getAllTasks());
    }
}

In this example:

Handling Updates and Task Modification

Extending this pattern, updating a task can follow a similar approach. For example, selecting a task from the list loads its details in editable fields. Upon user modification and clicking “Save,” the controller updates the model through the service.

Concurrency and Responsiveness

JavaFX runs all UI updates on a special thread called the JavaFX Application Thread. Long-running operations on this thread block the UI, causing it to freeze and become unresponsive. To maintain a smooth user experience, any expensive business logic—such as database access or network calls—should run on background threads.

JavaFX provides concurrency utilities like Task and Service to run background tasks and update the UI safely once complete.

Example: Running a long-running save operation in the background

Task<Void> saveTask = new Task<>() {
    @Override
    protected Void call() throws Exception {
        taskService.saveToDatabase();
        return null;
    }
};

saveTask.setOnSucceeded(event -> {
    // Update UI after save completes
    refreshTaskList();
});

new Thread(saveTask).start();

This pattern ensures the UI remains responsive during data processing, improving user experience.

Best Practices for Clean UI-Logic Integration

  1. Keep UI Controllers Thin: Avoid placing complex business logic in controllers. Delegate to service classes to promote separation of concerns.

  2. Use Observable Collections: Bind UI components like ListView or TableView to observable collections to automatically reflect changes in the underlying data.

  3. Decouple with Interfaces: Use interfaces for services and repositories, allowing easy substitution for testing or different implementations.

  4. Handle Validation Gracefully: Validate user input before calling business logic and provide clear feedback via the UI.

  5. Employ MVVM or MVP Patterns: For more complex apps, consider design patterns like Model-View-ViewModel (MVVM) or Model-View-Presenter (MVP) to further isolate UI and logic.

Challenges in Integration

Summary

Integrating JavaFX UI components with business logic classes involves handling UI events, delegating operations to service classes, and updating the UI based on changes. Using observable collections, background threads, and clear separation between UI and business logic leads to maintainable, responsive applications.

By following best practices, developers can build rich JavaFX applications where the UI and logic coexist cleanly, offering users a smooth experience and developers an organized codebase.

Index