Skip to content

Instantly share code, notes, and snippets.

@jonathasrochadesouza
Created October 1, 2026 11:40
Show Gist options
  • Select an option

  • Save jonathasrochadesouza/b750283992d93b5cd5d4a925934a283c to your computer and use it in GitHub Desktop.

Select an option

Save jonathasrochadesouza/b750283992d93b5cd5d4a925934a283c to your computer and use it in GitHub Desktop.
Hands-on Java SOLID principles challenge: refactor a flawed checkout flow by applying all five SOLID principles.

Java SOLID Refactoring Challenge

A hands-on challenge: refactor a working but deliberately flawed checkout flow to apply all five SOLID principles.

Original source

View the Java challenge in java-design-patterns

The accompanying SolidCheckoutChallenge.java contains the starting code. Payment, database, and email integrations are simulations.

Your mission

Improve the design while preserving successful checkout behavior. Create your own solution—this gist intentionally does not include one.

Principle Refactoring goal
Single responsibility Separate pricing, payment, persistence, and notification responsibilities.
Open/closed Add payment methods and pricing policies without modifying checkout orchestration.
Liskov substitution Ensure implementations honor every contract they advertise.
Interface segregation Avoid forcing payment-only vouchers to support refunds or reports.
Dependency inversion Depend on abstractions and allow dependencies to be supplied externally.

Business rules

  • Amounts use positive integer cents.
  • REGULAR customers pay full price.
  • VIP customers receive a 10% discount using integer division.
  • CARD and PIX support payment and refund.
  • VOUCHER supports payment only.
  • Save and notify exactly once after successful payment.
  • Validation or payment failures must not save or notify.

Run the starting code

The Java file declares package com.jonathas.solid. From the folder containing the downloaded file:

javac -d out SolidCheckoutChallenge.java
java -cp out com.jonathas.solid.SolidCheckoutChallenge

Acceptance checklist

  • REGULAR CARD: 10000 cents produces a 10000-cent charge.
  • VIP PIX: 10000 cents produces a 9000-cent charge.
  • Voucher payment succeeds without advertising refund capabilities.
  • CARD and PIX refunds honor a valid refundable contract.
  • Invalid totals produce no persistence or notification.
  • Payment failures produce no persistence or notification.
  • Checkout can be tested with in-memory fakes.
  • New payment methods and pricing policies require no changes to checkout orchestration.

Suggested workflow

  1. Draw the current dependencies.
  2. Identify the design violations.
  3. Write tests for existing behavior.
  4. Refactor incrementally.
  5. Add a new payment method and pricing policy to validate extensibility.

Keep the original file as a baseline and place your solution separately.

package com.jonathas.solid;
import java.util.ArrayList;
import java.util.List;
/**
* SOLID CHECKOUT CHALLENGE -- intentionally bad design, not production code.
* Run from project root (with Maven):
* mvn exec:java -Dexec.mainClass="com.jonathas.solid.SolidCheckoutChallenge"
* Or compile manually:
* javac -d target/classes src/main/java/com/jonathas/solid/SolidCheckoutChallenge.java
* java -cp target/classes com.jonathas.solid.SolidCheckoutChallenge
*
* MISSION: Refactor into multiple files and apply ALL FIVE SOLID principles.
* Keep the successful checkout behavior, but fix invalid capability contracts.
* Do not merely move methods or replace switches with another central registry.
*
* BUSINESS RULES:
* - Amounts are positive integer cents; reject zero/negative totals.
* - REGULAR pays full price; VIP gets 10% off using integer division.
* - CARD and PIX support payment and refund. VOUCHER supports payment only.
* - Save and notify exactly once after a successful payment.
* - Failed validation/payment must not save or notify.
* - Persistence and delivery below are fake; do not add real integrations.
*
* ACCEPTANCE CHALLENGES:
* 1. SRP: Pricing, payment, persistence, and notifications change independently.
* 2. OCP: Add a new payment method and customer pricing policy without editing
* the checkout orchestration or existing implementations.
* 3. LSP: Every implementation honors its advertised contracts. Never make
* checkout inspect concrete subtypes to work around unsupported behavior.
* 4. ISP: A payment-only voucher must not implement refund/report operations.
* 5. DIP: Test checkout using in-memory fakes, with no concrete integration
* constructed inside the checkout orchestrator.
*
* TESTS TO WRITE:
* - REGULAR CARD: 10000 -> 10000 cents; VIP PIX: 10000 -> 9000 cents.
* - Voucher checkout succeeds; unsupported refund is not advertised.
* - CARD/PIX refund succeeds through a valid refundable contract.
* - Invalid totals and payment failures produce no save/notification.
* - A new method/policy works without modifying checkout orchestration.
*
* Start by drawing the dependencies and identifying each violation.
* Keep this original as a baseline; put your solution in a separate folder.
*/
public class SolidCheckoutChallenge {
public static void main(String[] args) {
CheckoutService checkout = new CheckoutService();
checkout.checkout("order-1", "REGULAR", "CARD", 10000);
checkout.checkout("order-2", "VIP", "PIX", 10000);
checkout.checkout("order-3", "REGULAR", "VOUCHER", 5000);
// Uncomment to expose the broken substitution contract:
// PaymentProcessor processor = new VoucherProcessor();
// processor.refund("order-3", 5000);
}
interface PaymentOperations {
void pay(String orderId, int cents);
void refund(String orderId, int cents);
String settlementReport();
}
// Contract: pay/refund accept positive cents for any supported order.
static class PaymentProcessor implements PaymentOperations {
public void pay(String orderId, int cents) {
System.out.println("CARD paid " + cents + " for " + orderId);
}
public void refund(String orderId, int cents) {
System.out.println("CARD refunded " + cents + " for " + orderId);
}
public String settlementReport() { return "CARD settlement"; }
}
static class PixProcessor extends PaymentProcessor {
public void pay(String orderId, int cents) {
System.out.println("PIX paid " + cents + " for " + orderId);
}
public void refund(String orderId, int cents) {
System.out.println("PIX refunded " + cents + " for " + orderId);
}
public String settlementReport() { return "PIX settlement"; }
}
static class VoucherProcessor extends PaymentProcessor {
public void pay(String orderId, int cents) {
System.out.println("VOUCHER paid " + cents + " for " + orderId);
}
public void refund(String orderId, int cents) {
throw new UnsupportedOperationException("Vouchers cannot be refunded");
}
public String settlementReport() {
throw new UnsupportedOperationException("Vouchers have no settlement report");
}
}
static class CheckoutService {
private final FakeSqlDatabase database = new FakeSqlDatabase();
private final FakeEmailClient email = new FakeEmailClient();
public void checkout(String orderId, String customerType,
String paymentType, int totalCents) {
if (totalCents <= 0) throw new IllegalArgumentException("Invalid total");
int chargedCents;
switch (customerType) {
case "REGULAR": chargedCents = totalCents; break;
case "VIP": chargedCents = totalCents - totalCents / 10; break;
default: throw new IllegalArgumentException("Unknown customer type");
}
PaymentProcessor processor;
switch (paymentType) {
case "CARD": processor = new PaymentProcessor(); break;
case "PIX": processor = new PixProcessor(); break;
case "VOUCHER": processor = new VoucherProcessor(); break;
default: throw new IllegalArgumentException("Unknown payment type");
}
processor.pay(orderId, chargedCents);
database.insert(orderId + ":" + chargedCents);
email.send("Order " + orderId + " confirmed: " + chargedCents);
}
}
static class FakeSqlDatabase {
private final List<String> rows = new ArrayList<>();
public void insert(String row) {
rows.add(row);
System.out.println("Saved: " + row);
}
}
static class FakeEmailClient {
public void send(String message) {
System.out.println("Email: " + message);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment