June 17, 2026 in code-smells5 minutes
A intermediate-level guide to Large Class: before-and-after java code and diagrams for a CS student.
Imagine you are in your kitchen and need to chop an onion. You reach for a knife, but instead of a simple blade, you pull out a heavy, oversized tool that includes a screwdriver, a spoon, a saw, and a bottle opener. It is bulky, difficult to grip, and because all these tools are attached to one handle, trying to use the saw might accidentally snap the spoon.
In software, we often fall into this same trap by creating “God Objects”—classes that try to do everything for everyone. You start with an Order class meant to track items, but soon you find yourself adding credit card details for payments and shipping addresses for logistics. When one class takes on too many roles, it becomes a Large Class.
public class Order {
private String orderId;
private List<String> items;
private double totalAmount;
// Order management
public void addItem(String item, double price) {
items.add(item);
totalAmount += price;
}
// Payment processing (Responsibility 2)
private String cardNumber;
private String expiryDate;
public void processPayment() {
System.out.println("Processing $" + totalAmount + " via card " + cardNumber);
}
// Shipping/Logistics (Responsibility 3)
private String shippingAddress;
private String carrierService;
public void scheduleShipping() {
System.out.println("Shipping to " + shippingAddress + " via " + carrierService);
}
}In the diagram below, notice how the Order class is forced to manage three entirely different domains: order tracking, payment data, and shipping logistics.
classDiagram
class Order {
-String orderId
-List~String~ items
-double totalAmount
-String cardNumber
-String expiryDate
-String shippingAddress
-String carrierService
+addItem(String, double)
+processPayment()
+scheduleShipping()
}
A Large Class creates three specific types of friction:
Order class is holding everything, a change to the payment logic might accidentally break something in the shipping logic because they share the same memory space or variables. This violates the Single Responsibility Principle (SRP), which states that a class should have only one reason to change.To fix this, we use a refactoring technique called Extract Class. Instead of one class doing everything, we identify groups of data and behavior that belong together and move them into their own specialized classes.
The goal is high “Cohesion”—a term meaning that the things inside a single class are closely related to one another. By moving payment details into a PaymentInfo class and shipping details into a ShippingInfo class, we achieve better cohesion.
public class Order {
private String orderId;
private List<String> items;
private double totalAmount;
private PaymentInfo paymentInfo;
private ShippingInfo shippingInfo;
public Order(PaymentInfo payment, ShippingInfo shipping) {
this.paymentInfo = payment;
this.shippingInfo = shipping;
}
public void addItem(String item, double price) {
items.add(item);
totalAmount += price;
}
public void processPayment() {
paymentInfo.process(totalAmount);
}
public void scheduleShipping() {
shippingInfo.ship();
}
}
class PaymentInfo {
private String cardNumber;
private String expiryDate;
public PaymentInfo(String card, String expiry) {
this.cardNumber = card;
this.expiryDate = expiry;
}
public void process(double amount) {
System.out.println("Processing $" + amount + " via card " + cardNumber);
}
}
class ShippingInfo {
private String address;
private String carrierService;
public ShippingInfo(String address, String carrier) {
this.address = address;
this.carrierService = carrier;
}
public void ship() {
System.out.println("Shipping to " + address + " via " + carrierService);
}
}Now, the Order class no longer manages strings like cardNumber. Instead, it simply holds references to its specialized helpers. The runtime interaction changes from one giant object doing everything to a coordinated effort between smaller objects:
sequenceDiagram
participant O as Order
participant P as PaymentInfo
participant S as ShippingInfo
O ->> P: process(totalAmount)
P -->> O:
O ->> S: ship()
S -->> O:
In this new structure, the Order class acts as a coordinator. It knows when to pay and when to ship, but it doesn’t need to know the messy details of how a credit card is validated or how a carrier service operates.
Extracting classes is highly effective when you notice “The And Problem.” If you describe your class as “This class manages orders AND payments AND shipping,” it is a clear candidate for refactoring.
However, do not be dogmatic about file length or line counts. Some classes are naturally large because they manage one very complex, singular process (like a complex state machine). Size itself isn’t the enemy; too many unrelated responsibilities is what creates technical debt. If a class is 200 lines long but only does one specific thing with high precision, it is likely fine.
Using the refactored Order class with specialized helper objects
PaymentInfo payment = new PaymentInfo("1234-5678", "12/25");
ShippingInfo shipping = new ShippingInfo("123 Java Lane", "FedEx");
Order order = new Order(payment, shipping);
order.addItem("Laptop", 1200.00);
order.processPayment();