Assignment Chef icon Assignment Chef
All English tutorials

Programming lesson

Mastering Java MVC with Compose: Building a Munro Quiz App

Learn how to build a modular Java application using the MVC pattern, interfaces, and dependency injection by creating a Munro quiz app. Hands-on tutorial for COMPSCI5092.

Java MVC tutorial Munro quiz app Java composition example dependency injection Java JavaFX tutorial JUnit testing Java COMPSCI5092 assignment help Java interface design professional Java development Java 17 best practices JavaFX MVC pattern Java object composition Java property file configuration Java remote service API Java code documentation Java test-driven development

Introduction: Why MVC and Composition Matter in Modern Java Development

In the world of software engineering, writing maintainable and testable code is a superpower. The Model-View-Controller (MVC) pattern is a cornerstone of professional development, and Java's strong typing makes it an excellent language to implement it. In this tutorial, we'll build a quiz app that tests your knowledge of the 282 Munros—Scotland's highest peaks—using a modular design. Think of it as assembling a team of specialists: a View handles the user interface, a Service fetches data, and a Controller orchestrates the logic. By composing objects with interfaces, you can swap implementations without breaking your code. This is exactly how modern apps like Instagram or Spotify manage features: they separate concerns so that changing a UI library or a data source doesn't require a full rewrite.

Understanding the MVC Architecture

MVC divides your application into three interconnected components:

  • Model: Represents the data and business logic. In our case, the Munro data (name, height, location).
  • View: Handles the user interface—displaying images, buttons, and text fields.
  • Controller: Acts as the middleman, responding to user input and updating the model or view.

But wait—in the assignment, you have a Service class instead of a Model. That's because the data comes from a remote service (like an API). The Service is part of the model layer. This separation allows you to later replace the remote service with a local database without touching the UI code. It's like swapping the engine of a car without changing the steering wheel.

Setting Up Your Project with Java 17 and JavaFX

Before diving into code, ensure you have the right tools. You'll need Java 17 SDK, JavaFX SDK 17, Hamcrest 2.2, and JUnit. Follow the setup guide in your course notes (DevelopmentTools.pdf). Once you have a clean template project, you're ready. Open the app.properties file located in src/main/resources/properties. This file lets you choose different implementations. For example:

# Choose View: SimpleView or ImageView
View=SimpleView
# Choose Service: LocalService or RemoteService
Service=RemoteService
# Choose Controller: SimpleController or QuizController
Controller=SimpleController

Change the Controller to QuizController and run the app. You'll see a random Munro image and a text field to guess its name. The app tells you if you're correct. This is your starting point.

Creating Interfaces for Flexibility

Interfaces define contracts. They specify what methods a class must implement without dictating how. In your project, you have interfaces for View, Service, and Controller. For instance:

public interface View {
    void displayImage(Image image);
    String getUserInput();
    void showResult(boolean correct);
}

public interface Service {
    Munro getRandomMunro();
    Image fetchImage(Munro munro);
}

public interface Controller {
    void start();
    void handleGuess();
    void nextMunro();
}

Now, any class that implements View must provide these methods. This allows you to swap a JavaFX-based view with a console-based view without changing the rest of the code. It's like having a universal remote that works with different TV brands.

Composing Objects: The Dependency Injection Pattern

Composition means building complex objects by combining simpler ones. Instead of inheriting behavior, you pass dependencies (like Service and View) into the Controller via its constructor. This is called dependency injection (DI). Example:

public class QuizController implements Controller {
    private final View view;
    private final Service service;
    private Munro currentMunro;

    public QuizController(View view, Service service) {
        this.view = view;
        this.service = service;
    }

    @Override
    public void start() {
        currentMunro = service.getRandomMunro();
        view.displayImage(service.fetchImage(currentMunro));
    }

    @Override
    public void handleGuess() {
        String guess = view.getUserInput();
        boolean correct = guess.equalsIgnoreCase(currentMunro.getName());
        view.showResult(correct);
    }
}

Notice that QuizController doesn't create its own View or Service. They are provided from outside. This makes testing easy: you can pass mock objects. For example, you can test the controller's logic without needing a real JavaFX window.

Implementing a Custom View with JavaFX

Let's create a simple ImageView class that displays the Munro picture and a text field. JavaFX makes this straightforward:

public class ImageView implements View {
    private Stage stage;
    private TextField inputField;
    private Label resultLabel;
    private ImageView imageView;

    public ImageView() {
        // Initialize JavaFX components
        // In a real app, you'd load an FXML or build the UI programmatically
    }

    @Override
    public void displayImage(Image image) {
        imageView.setImage(image);
    }

    @Override
    public String getUserInput() {
        return inputField.getText();
    }

    @Override
    public void showResult(boolean correct) {
        resultLabel.setText(correct ? "Correct!" : "Wrong!");
    }
}

You can also implement a ConsoleView that uses System.out and Scanner. This is great for testing or when you don't need a GUI.

Working with Remote Services

The Service interface abstracts data retrieval. A RemoteService might fetch Munro data from an API like walkhighlands.co.uk. A LocalService could read from a file. Here's a skeleton:

public class RemoteService implements Service {
    private HttpClient client;

    public RemoteService() {
        client = HttpClient.newHttpClient();
    }

    @Override
    public Munro getRandomMunro() {
        // Call API endpoint to get random Munro
        // Parse JSON response
        return new Munro("Ben Nevis", 1345, "Lochaber");
    }

    @Override
    public Image fetchImage(Munro munro) {
        // Fetch image from URL
        return new Image(munro.getImageUrl());
    }
}

To switch between services, just change the app.properties file. No code changes needed.

Testing with JUnit and Hamcrest

Testing is crucial. Use JUnit 5 and Hamcrest matchers for expressive assertions. For example, test the controller's guess logic:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

public class QuizControllerTest {
    @Test
    public void testCorrectGuess() {
        // Arrange
        View mockView = mock(View.class);
        Service mockService = mock(Service.class);
        when(mockService.getRandomMunro()).thenReturn(new Munro("Ben Nevis"));
        QuizController controller = new QuizController(mockView, mockService);
        when(mockView.getUserInput()).thenReturn("Ben Nevis");

        // Act
        controller.start();
        controller.handleGuess();

        // Assert
        verify(mockView).showResult(true);
    }
}

Using mocks (like Mockito) isolates the controller from real dependencies. This is a professional practice.

Putting It All Together: The Property File as a Configuration Tool

The app.properties file acts as a simple IoC (Inversion of Control) container. Your main application reads this file and instantiates the appropriate classes. Example:

public class App {
    public static void main(String[] args) {
        Properties props = loadProperties();
        View view = createView(props.getProperty("View"));
        Service service = createService(props.getProperty("Service"));
        Controller controller = createController(props.getProperty("Controller"), view, service);
        controller.start();
    }
}

This is a lightweight alternative to frameworks like Spring. It teaches you the fundamentals of DI.

Extending the App: Adding a Quiz Timer (Like a Game Show)

To make it more engaging, add a timer feature. Create a TimedController that extends QuizController and uses a ScheduledExecutorService to limit guess time. This is similar to how quiz apps on TikTok or Instagram Stories work—they keep users engaged with time pressure. You can swap the controller in the property file to switch between quiz modes.

Documenting Your Code with Javadoc

Professional code includes documentation. Use Javadoc comments for classes and methods:

/**
 * Controller for the Munro quiz game.
 * Uses dependency injection for View and Service.
 */
public class QuizController implements Controller {
    // ...
}

Generate HTML docs with javadoc command. This is a requirement in COMPSCI5092.

Conclusion: From Munros to Modern Apps

By applying MVC, interfaces, and composition, you've built a flexible, testable application. These skills transfer directly to enterprise Java development, Android apps, and even game development. The next time you use an app like Strava (which tracks hill climbs) or a language-learning app like Duolingo, notice how they separate concerns. You're now equipped to design systems that can evolve without breaking. Happy coding, and may you conquer all 282 Munros—in code and in spirit!