Table of Contents
Type: Coursework | Subject: Computer Science | Level: Undergraduate | Word Count: ~3,000 words
This model coursework was produced by an Essays UK specialist as reference material for learning purposes only. For support in this field, see our computer science coursework writers.
Overview: This model undergraduate coursework works through the analysis, object-oriented design and partial Java implementation of an inventory management system for a small retail business. It demonstrates how to translate a brief into requirements, model the solution with use case, class, sequence and state UML diagrams, and justify design decisions using established principles and patterns. It is intended purely as a worked example of software design coursework.
Inventory management is a recurring problem domain in introductory software engineering because it is small enough to model completely yet rich enough to exercise the full object-oriented toolkit. This coursework responds to a brief that asks for the design of a desktop inventory management system for a small independent retailer that currently records stock on paper. The retailer wants to track products, monitor stock levels, record sales and deliveries, and receive warnings when an item needs reordering. The purpose of this document is not to deliver a finished commercial product but to demonstrate a disciplined design process: moving from an informal brief to a set of testable requirements, modelling the solution with the Unified Modeling Language (UML), and showing how the resulting classes would be realised in Java.
The work is organised to mirror the early phases of an iterative development lifecycle. It begins by clarifying scope and gathering requirements, then presents the system architecture and the UML models that describe structure and behaviour. It subsequently explains how object-oriented principles and design patterns shaped the model, illustrates several implementation decisions in Java, and closes with a test strategy and a critical reflection. Throughout, design choices are justified rather than merely stated.
The brief describes a single-user application running on one workstation behind the shop counter. There is no requirement for a web front end, a multi-user network deployment or integration with an external accounting package, so these are explicitly excluded from scope. Including them would be speculative and would dilute the focus on core design skills that the assessment criteria reward. Keeping scope tight is itself a design decision: it reduces accidental complexity and makes the system easier to reason about and to test.
Within scope, the system must maintain a catalogue of products, allow stock quantities to be adjusted through sales and deliveries, flag items that fall below a reorder threshold, and persist data between sessions so that information survives the application being closed. A simple reporting feature that lists low-stock items is also required. These boundaries frame every subsequent decision, and the requirements below make them concrete and verifiable.
Requirements were separated into functional requirements, which describe what the system must do, and non-functional requirements, which describe qualities the system must exhibit. This separation follows standard practice and helps ensure that quality attributes are not forgotten once the visible features are specified. Each requirement is phrased so that it can later be confirmed or refuted by a test.
The maintainability requirement is particularly influential. Anticipating that the persistence mechanism might change encourages a design in which the storage code is isolated behind an interface, a decision revisited when the data access pattern is discussed below.
A layered architecture was adopted, separating the system into a presentation layer, a service or domain layer, and a persistence layer. The presentation layer handles interaction with the user; the domain layer contains the entities and the business rules that govern them; and the persistence layer is responsible for reading and writing data. Layering is a well-established way of managing dependencies, because each layer depends only on the one beneath it, and the domain logic remains ignorant of both how it is displayed and how it is stored.
This structure brings two practical benefits for the brief. First, it directly satisfies the maintainability requirement, since replacing the persistence layer affects nothing above it. Second, it makes the domain logic independently testable: the rules for rejecting an oversized sale can be exercised without any user interface or file access at all. The arrangement loosely follows the Model-View-Controller idea, where the model is the domain layer, the view is the presentation layer, and a controller mediates between them, although for a single-user desktop tool the controller responsibilities are kept deliberately light.
UML was used to capture the system from complementary viewpoints. A use case diagram defines what the system does for its user; a class diagram defines its static structure; a sequence diagram shows how objects collaborate over time to satisfy a use case; and a state diagram captures the lifecycle of a key object. Using several diagram types is deliberate, because no single view can express both structure and behaviour clearly.
The use case model has a single actor, the Shop Assistant, who interacts with the system through five use cases: Manage Products, Record Sale, Record Delivery, View Low-Stock Report and Save Data. Manage Products is a general case that, in a more detailed model, would include the Add Product, Edit Product and Remove Product behaviours through an include relationship, because each of those shares the common step of locating and validating product details. Record Sale carries the most interesting business rule, since it must fail cleanly when stock is insufficient; this alternative flow is documented in the use case description rather than buried in code, so that it is visible to a reviewer. Modelling the actor explicitly also reinforces the single-user scope, as there is no Administrator or Manager role in this version of the system.
The class diagram is the heart of the design. The core domain classes and their principal responsibilities are summarised in the table below, which illustrates how responsibilities were distributed so that each class has a single, coherent purpose.
| Class | Responsibility | Key Attributes / Methods |
|---|---|---|
| Product | Represents a single catalogue item and enforces its own validity | id, name, category, unitPrice, quantity, reorderLevel; needsReorder() |
| Transaction (abstract) | Captures a stock movement common to sales and deliveries | timestamp, product, quantity; apply() |
| Sale | A transaction that reduces stock | extends Transaction; overrides apply() |
| Delivery | A transaction that increases stock | extends Transaction; overrides apply() |
| Inventory | Holds the product catalogue and applies business rules | addProduct(), recordSale(), recordDelivery(), lowStockItems() |
| InventoryRepository (interface) | Abstracts storage of the inventory | load(), save() |
Several relationships are worth noting. The Inventory class holds a collection of Product objects in a composition relationship, meaning the products are owned by, and have no meaningful existence outside, the inventory. Sale and Delivery both generalise the abstract Transaction class, which is where inheritance and polymorphism enter the design. Crucially, Inventory depends on the InventoryRepository interface rather than on any concrete storage class, so the arrow of dependency points at an abstraction. This single decision is what allows the persistence mechanism to be swapped without touching the domain, and it is a deliberate application of the dependency inversion principle discussed later.
A sequence diagram was produced for Record Sale because it is the use case with the richest interaction and the most important business rule. The interaction begins when the user submits a sale through the presentation layer, which calls recordSale on the Inventory object, passing a product identifier and a quantity. Inventory locates the matching Product and queries its current quantity. If the requested quantity exceeds what is available, Inventory does not mutate any state; instead it returns a failure outcome that the presentation layer translates into a clear on-screen message. If sufficient stock exists, Inventory constructs a Sale object, invokes its apply method to decrement the product quantity, stores the transaction, and returns success. Representing the failure path as a separate fragment on the diagram makes explicit that the system never enters an inconsistent state, which addresses the reliability requirement at the design stage rather than leaving it to be discovered in testing.
A state diagram was used to describe the lifecycle of a product from a stock perspective. A product moves between three states: In Stock when its quantity is above the reorder level, Low Stock when it is at or below that level but still greater than zero, and Out of Stock when the quantity reaches zero. Transitions are triggered by sales and deliveries. Modelling these states clarifies exactly when the low-stock report should include an item and prevents the ambiguity that often arises when such rules live only as scattered conditional statements.
The UML models are not arbitrary; they are shaped by object-oriented principles that promote code which is easier to understand, extend and maintain. The most influential principles for this design are discussed below.
Encapsulation is applied by keeping every attribute of Product private and exposing behaviour through methods. Rather than letting external code set the quantity directly, the design routes all stock changes through the Inventory and Transaction classes, so the validity of the data is protected at all times. Abstraction complements this by exposing only what callers need: a user of the Inventory sees meaningful operations such as recordSale and lowStockItems, not the internal data structure that stores products. This narrow public surface reduces the ways the system can be misused and makes it safer to change the hidden internals later.
Inheritance is used sparingly and only where a genuine specialisation exists. Sale and Delivery are both kinds of Transaction, and each overrides the apply method to change stock in the appropriate direction. Polymorphism then allows the Inventory to hold a collection of Transaction references and process them uniformly without knowing or caring whether each is a sale or a delivery. This avoids the brittle chains of type-checking conditionals that often appear in procedural designs and means that a future transaction type, such as a stock write-off, could be added by creating one new subclass rather than by editing existing logic.
The design was reviewed against the SOLID principles, which are widely used as a checklist for maintainable object-oriented code:
apply method.Checking a design against SOLID is not about mechanical compliance; it is a way of surfacing fragile dependencies before they become expensive to fix. Here the review confirmed that the dependency inversion already built into the class diagram was carrying much of the maintainability requirement.
Three recognised design patterns were incorporated where they solved a real problem rather than for their own sake, as summarised below.
| Pattern | Where Applied | Problem It Solves |
|---|---|---|
| Data Access Object (DAO) | InventoryRepository and its file-based implementation | Isolates persistence so the domain is independent of storage technology |
| Factory Method | Creation of Sale and Delivery transactions | Centralises object creation and keeps construction logic out of the Inventory |
| Observer | Notifying the low-stock report when quantities change | Lets the report update without the domain knowing about the user interface |
The DAO pattern is the most important of the three and is the concrete realisation of the dependency inversion principle in the persistence layer. The Observer pattern, by contrast, was included cautiously, because for a single-user tool it risks over-engineering; the reflection section returns to it.
Although the brief is primarily a design exercise, representative fragments of Java are included to show that the model is realisable and to demonstrate language features that support good design. The Product class encapsulates its state and exposes a small, meaningful interface:
public class Product {
private final String id;
private String name;
private int quantity;
private int reorderLevel;
public boolean needsReorder() {
return quantity <= reorderLevel;
}
void adjustQuantity(int delta) {
int updated = quantity + delta;
if (updated < 0) {
throw new IllegalArgumentException("Insufficient stock");
}
quantity = updated;
}
}
The adjustQuantity method has package-private visibility so that only classes within the domain, such as the transaction classes, may change stock levels; external callers must go through the Inventory. The abstract Transaction class defines the shared contract and defers the direction of the change to its subclasses:
public abstract class Transaction {
protected final Product product;
protected final int quantity;
protected Transaction(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
}
public abstract void apply();
}
A Sale then supplies its own behaviour by overriding apply to subtract stock, while a Delivery adds stock. Because both conform to the same abstraction, the Inventory can treat them uniformly, which is polymorphism delivering a concrete maintainability benefit rather than a textbook abstraction.
Persistence is provided through a concrete class that implements the InventoryRepository interface by serialising the catalogue to a local file and reading it back on start-up. Because the domain depends only on the interface, this file-based implementation can later be replaced by one backed by a relational database or an embedded store with no change to the Inventory or the entities. A lightweight, human-readable format such as JSON is convenient during development because it aids diagnosis and makes it easy to seed known data for tests. Error handling in this layer is important for reliability: a failure to read the file at start-up should produce an empty but usable catalogue and a clear warning, rather than preventing the application from launching, and a failure to write should surface an explicit message rather than silently losing a transaction.
A set of unit tests was planned to validate the business rules independently of the user interface, which is possible precisely because the domain layer has no presentation or persistence dependencies. Representative test cases are shown below.
| Test Case | Input | Expected Result |
|---|---|---|
| Valid sale | Sell 3 units of a product holding 10 | Quantity becomes 7; sale recorded |
| Oversized sale | Sell 12 units of a product holding 10 | Sale rejected; quantity unchanged at 10 |
| Delivery | Deliver 5 units of a product holding 2 | Quantity becomes 7 |
| Reorder boundary | Quantity falls to the reorder level | Product appears in the low-stock report |
| Persistence round trip | Save catalogue, reload into a new Inventory | Restored data matches the saved data |
The oversized sale and reorder boundary cases are the most valuable because they test the rules at their edges, where defects most often hide. Testing the persistence round trip confirms that the reliability requirement is met in practice and not merely asserted in the design. These tests would be automated so that they can be run repeatedly as the code evolves, giving confidence that a later change has not broken an existing rule.
Assessed against the brief, the design satisfies each functional requirement and addresses the non-functional requirements through specific decisions: layering and the DAO pattern for maintainability, isolated domain logic for testability, and careful error handling for reliability. The clearest strength of the design is the separation between the domain and its storage, which gives the system room to grow without rework. The use of a small abstract Transaction hierarchy also keeps the stock-movement logic open to extension.
The design is not beyond criticism. The Observer pattern, while a reasonable way to decouple the report from the domain, adds indirection that a single-user tool may not need; a simpler approach that recomputes the report on demand could be adequate and easier to follow, and a more mature judgement might defer the pattern until a second view genuinely required it. The decision to keep the model single-user also limits the system, and a realistic retail setting would eventually need concurrent access, which would change the persistence and locking strategy considerably. Finally, the absence of a dedicated identifier generator means product identifiers are assumed to be supplied correctly; a production design would assign and guarantee their uniqueness internally. Recognising these limitations is part of the exercise, because a design is strongest when its author understands precisely where it would need to change.
If the work were extended, the most valuable next steps would be to replace file storage with an embedded database to improve resilience, to introduce a thin controller to keep the presentation layer free of logic, and to broaden the automated test suite to cover the persistence error paths. Each of these follows naturally from the architecture already in place.
This coursework has demonstrated a complete, if deliberately scoped, design process for an inventory management system. Beginning from an informal brief, it derived testable functional and non-functional requirements, chose a layered architecture to control dependencies, and modelled the solution from several viewpoints using use case, class, sequence and state diagrams. It showed how encapsulation, inheritance, polymorphism and the SOLID principles shaped the class model, and how the DAO, Factory Method and Observer patterns addressed specific problems. Selected Java fragments confirmed that the model is implementable, and a test strategy showed how its rules would be validated. Above all, the work has treated design as a sequence of justified decisions, each traceable to a requirement, and has reflected honestly on where the design would need to evolve. That combination of structured modelling and critical judgement is the central skill the brief sets out to assess.
You May Also Like