Part B: Appointment Scheduling - Mediator Pattern
Document: Part B Analysis
Pattern: Mediator Pattern
Module: Appointment Scheduling System
Author: Ishara Lakshitha
Table of Contents
1. Overview & Problem Statement
Healthcare Domain Problems Addressed
Problem 1: Complex Scheduling Logic
- Multiple entities involved: Patients, Doctors, Appointments, Schedules
- Complex business rules for conflict detection
- Time slot validation with 30-minute appointment windows
- Integration with database persistence layer
Problem 2: Tight Coupling Between Components
- Without mediation, each component would need direct references to others
- Patient scheduling would require knowledge of Doctor availability
- Doctor objects would need to manage their own appointment conflicts
- Database operations scattered across multiple classes
Problem 3: Centralized Business Rule Management
- Appointment validation rules need consistent application
- Conflict detection algorithms require centralized logic
- Status management and workflow coordination
- Error handling and feedback messaging
Solution Approach
The Mediator Pattern implementation centralizes all complex scheduling interactions through the AppointmentScheduler class, which acts as the mediator between:
- Patient booking requests
- Doctor availability checking
- Appointment conflict detection
- Database persistence operations
2. Mediator Pattern Implementation
2.1 Pattern Participants
Mediator Interface (Implicit)
// The mediator responsibilities are fulfilled by AppointmentScheduler
// Could be made explicit with an interface, but concrete implementation suffices
public interface SchedulingMediator {
String bookAppointment(String patientId, Doctor doctor,
LocalDateTime requestedDateTime, String reason, String doctorNotes);
}
Concrete Mediator
/**
* The Mediator. It handles the complex logic of scheduling,
* checking for conflicts, and coordinating between the database and the request.
*/
public class AppointmentScheduler {
private final SchedulingDAO schedulingDAO;
public AppointmentScheduler() {
this.schedulingDAO = new SchedulingDAO();
}
/**
* Central coordination method for appointment booking
* Encapsulates all complex scheduling logic
*/
public String bookAppointment(String patientId, Doctor doctor,
LocalDateTime requestedDateTime, String reason,
String doctorNotes) {
// Step 1: Retrieve existing appointments for conflict checking
List<Appointment> existingAppointments =
schedulingDAO.getAppointmentsForDoctorOnDate(doctor.getDoctorId(),
requestedDateTime.toLocalDate());
// Step 2: Apply business rules for conflict detection
for (Appointment existing : existingAppointments) {
LocalDateTime existingStart = existing.getAppointmentDateTime();
// 30-minute conflict rule implementation
if (requestedDateTime.isAfter(existingStart.minusMinutes(30)) &&
requestedDateTime.isBefore(existingStart.plusMinutes(30))) {
return "Booking failed: Time slot conflicts with an existing appointment (30-min rule).";
}
}
// Step 3: Create and persist new appointment
Appointment newAppointment = new Appointment(patientId, doctor.getDoctorId(),
requestedDateTime, reason);
newAppointment.setDoctorNotes(doctorNotes);
boolean success = schedulingDAO.createAppointment(newAppointment);
return success ? "Appointment booked successfully!" :
"Booking failed: Could not save to database.";
}
}
Colleague Classes
Appointment (Domain Entity)
public class Appointment implements Visitable {
private int appointmentId;
private String patientId;
private String doctorId;
private LocalDateTime appointmentDateTime;
private String reason;
private String status;
private String doctorNotes;
private LocalDateTime lastUpdated;
private String lastUpdatedBy;
public Appointment(String patientId, String doctorId,
LocalDateTime appointmentDateTime, String reason) {
this.patientId = patientId;
this.doctorId = doctorId;
this.appointmentDateTime = appointmentDateTime;
this.reason = reason;
this.status = "Scheduled"; // Default status
this.doctorNotes = "";
this.lastUpdated = LocalDateTime.now();
this.lastUpdatedBy = "system";
}
// Getters and setters...
}
Doctor (Domain Entity)
public class Doctor {
private String doctorId;
private String fullName;
private String specialty;
public Doctor(String doctorId, String fullName, String specialty) {
this.doctorId = doctorId;
this.fullName = fullName;
this.specialty = specialty;
}
// Getters and setters...
}
SchedulingDAO (Data Access Layer)
public class SchedulingDAO {
public List<Appointment> getAppointmentsForDoctorOnDate(String doctorId, LocalDate date) {
// Database query implementation
}
public boolean createAppointment(Appointment appointment) {
// Database persistence implementation
}
public List<Doctor> getAllDoctors() {
// Doctor retrieval implementation
}
}
3. UML Class Diagrams
3.0 Comprehensive Diagram

(You can find the High Res images in the Github Project Repo)
3.1 Mediator Pattern Structure
┌─────────────────────────────────────┐
│ AppointmentController │
│ (Client/Colleague) │
├─────────────────────────────────────┤
│ - scheduler: AppointmentScheduler │
│ - dao: SchedulingDAO │
│ - view: AppointmentPanel │
├─────────────────────────────────────┤
│ + bookNewAppointment(): void │
│ + viewSchedule(): void │
│ + cancelAppointment(): void │
└─────────────────────────────────────┘
│
│ uses
▼
┌─────────────────────────────────────┐
│ AppointmentScheduler │
│ (Mediator) │
├─────────────────────────────────────┤
│ - schedulingDAO: SchedulingDAO │
├─────────────────────────────────────┤
│ + bookAppointment(): String │
└─────────────────────────────────────┘
│
│ coordinates
▼
┌─────────────────────────────────────┐ ┌─────────────────────────────────────┐
│ Appointment │ │ Doctor │
│ (Colleague) │ │ (Colleague) │
├─────────────────────────────────────┤ ├─────────────────────────────────────┤
│ - appointmentId: int │ │ - doctorId: String │
│ - patientId: String │ │ - fullName: String │
│ - doctorId: String │ │ - specialty: String │
│ - appointmentDateTime: LocalDateTime│ ├─────────────────────────────────────┤
│ - reason: String │ │ + getDoctorId(): String │
│ - status: String │ │ + getFullName(): String │
│ - doctorNotes: String │ │ + getSpecialty(): String │
├─────────────────────────────────────┤ └─────────────────────────────────────┘
│ + getAppointmentDateTime(): LocalDT │
│ + setStatus(status): void │
│ + setDoctorNotes(notes): void │
└─────────────────────────────────────┘
▲
│ manages
│
┌─────────────────────────────────────┐
│ SchedulingDAO │
│ (Data Access) │
├─────────────────────────────────────┤
│ + getAppointmentsForDoctorOnDate() │
│ + createAppointment(): boolean │
│ + updateAppointment(): boolean │
│ + getAllDoctors(): List<Doctor> │
└─────────────────────────────────────┘
3.2 Interaction Flow Diagram
Client Request → Controller → Mediator → Database/Entities → Response
AppointmentController.bookNewAppointment()
│
▼
AppointmentScheduler.bookAppointment()
│
├─→ SchedulingDAO.getAppointmentsForDoctorOnDate()
│ │
│ ▼
│ [Conflict Detection Logic]
│ │
├─→ new Appointment()
│ │
└─→ SchedulingDAO.createAppointment()
│
▼
[Success/Failure Response]
4. Detailed Code Analysis
4.1 Conflict Detection Algorithm
/**
* Core business logic: 30-minute appointment window conflict detection
* This algorithm prevents overlapping appointments by ensuring a 30-minute
* buffer around each existing appointment.
*/
public String bookAppointment(String patientId, Doctor doctor,
LocalDateTime requestedDateTime, String reason,
String doctorNotes) {
// Retrieve all appointments for the doctor on the requested date
List<Appointment> existingAppointments =
schedulingDAO.getAppointmentsForDoctorOnDate(doctor.getDoctorId(),
requestedDateTime.toLocalDate());
// Apply 30-minute conflict rule
for (Appointment existing : existingAppointments) {
LocalDateTime existingStart = existing.getAppointmentDateTime();
/*
* Conflict scenarios:
* - Existing at 10:00, Requested at 10:15 → CONFLICT (within 30 min)
* - Existing at 10:00, Requested at 09:45 → CONFLICT (within 30 min)
* - Existing at 10:00, Requested at 10:30 → NO CONFLICT (exactly 30 min)
* - Existing at 10:00, Requested at 09:30 → NO CONFLICT (exactly 30 min)
*/
if (requestedDateTime.isAfter(existingStart.minusMinutes(30)) &&
requestedDateTime.isBefore(existingStart.plusMinutes(30))) {
return "Booking failed: Time slot conflicts with an existing appointment (30-min rule).";
}
}
// No conflicts found - proceed with booking
Appointment newAppointment = new Appointment(patientId, doctor.getDoctorId(),
requestedDateTime, reason);
newAppointment.setDoctorNotes(doctorNotes);
boolean success = schedulingDAO.createAppointment(newAppointment);
return success ? "Appointment booked successfully!" :
"Booking failed: Could not save to database.";
}
4.2 Data Access Coordination
/**
* The mediator coordinates with the DAO layer to retrieve necessary data
* for conflict checking without exposing database operations to the client
*/
public List<Appointment> getAppointmentsForDoctorOnDate(String doctorId, LocalDate date) {
List<Appointment> appointments = new ArrayList<>();
String sql = "SELECT * FROM appointments WHERE doctor_id = ? AND DATE(appointment_datetime) = ?";
try (Connection conn = DatabaseManager.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, doctorId);
pstmt.setDate(2, Date.valueOf(date));
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
Appointment appt = new Appointment(
rs.getString("patient_id"),
rs.getString("doctor_id"),
rs.getTimestamp("appointment_datetime").toLocalDateTime(),
rs.getString("reason")
);
appt.setAppointmentId(rs.getInt("appointment_id"));
appt.setStatus(rs.getString("status"));
appt.setDoctorNotes(rs.getString("doctor_notes"));
appointments.add(appt);
}
} catch (SQLException e) {
System.err.println("Error fetching appointments for doctor " +
doctorId + " on " + date + ": " + e.getMessage());
}
return appointments;
}
4.3 Object Creation and State Management
/**
* The mediator handles object creation and initial state setup
* ensuring consistent object initialization across the system
*/
Appointment newAppointment = new Appointment(patientId, doctor.getDoctorId(),
requestedDateTime, reason);
// Set additional properties through mediator logic
newAppointment.setDoctorNotes(doctorNotes);
// Status is automatically set to "Scheduled" in constructor
// lastUpdated and lastUpdatedBy are set automatically
// Persistence is handled through the mediator's DAO coordination
boolean success = schedulingDAO.createAppointment(newAppointment);
5. Complex Interaction Management
5.1 Multi-Step Workflow Coordination
The mediator manages a complex workflow that involves multiple steps:
/**
* Complete appointment booking workflow managed by the mediator:
* 1. Data Retrieval
* 2. Business Rule Validation
* 3. Object Creation
* 4. Persistence
* 5. Response Generation
*/
public String bookAppointment(...) {
// Step 1: Data Retrieval
List<Appointment> existingAppointments = schedulingDAO.getAppointmentsForDoctorOnDate(...);
// Step 2: Business Rule Validation
for (Appointment existing : existingAppointments) {
if (hasConflict(requestedDateTime, existing.getAppointmentDateTime())) {
return generateConflictMessage();
}
}
// Step 3: Object Creation
Appointment newAppointment = createAppointment(...);
// Step 4: Persistence
boolean success = schedulingDAO.createAppointment(newAppointment);
// Step 5: Response Generation
return generateResponseMessage(success);
}
5.2 Integration with Controller Layer
/**
* AppointmentController delegates complex scheduling to the mediator
* while handling UI concerns and user permissions
*/
public class AppointmentController {
private final AppointmentScheduler scheduler; // Mediator instance
private void bookNewAppointment() {
// UI validation and data collection
String patientId = view.patientIdField.getText().trim();
String reason = view.reasonField.getText().trim();
String doctorNotes = view.getDoctorNotesText();
// Permission checking
if (!currentUser.hasPermission("can_book_appointment")) {
showPermissionError();
return;
}
// Doctor selection logic
Doctor selectedDoctor = getSelectedDoctor();
LocalDateTime requestedDateTime = getRequestedDateTime();
// Delegate to mediator for complex scheduling logic
String resultMessage = scheduler.bookAppointment(
patientId, selectedDoctor, requestedDateTime, reason, doctorNotes
);
// Handle response and update UI
JOptionPane.showMessageDialog(view, resultMessage +
"\\nBooked by: " + currentUser.getUsername());
viewSchedule(); // Refresh display
view.clearBookingFormFields();
}
}
5.3 Database Transaction Coordination
/**
* The mediator can coordinate database transactions across multiple operations
* ensuring data consistency for complex scheduling scenarios
*/
public boolean createAppointment(Appointment appointment) {
String sql = "INSERT INTO appointments (patient_id, doctor_id, appointment_datetime, " +
"reason, doctor_notes, status) VALUES (?, ?, ?, ?, ?, ?)";
try (Connection conn = DatabaseManager.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, appointment.getPatientId());
pstmt.setString(2, appointment.getDoctorId());
pstmt.setTimestamp(3, Timestamp.valueOf(appointment.getAppointmentDateTime()));
pstmt.setString(4, appointment.getReason());
pstmt.setString(5, appointment.getDoctorNotes());
pstmt.setString(6, appointment.getStatus() != null ?
appointment.getStatus() : "Scheduled");
boolean success = pstmt.executeUpdate() > 0;
if (success) {
System.out.println("SUCCESS: Created appointment for patient " +
appointment.getPatientId() + " with doctor " +
appointment.getDoctorId());
}
return success;
} catch (SQLException e) {
System.err.println("ERROR: Error creating appointment: " + e.getMessage());
return false;
}
}
6. Usage Scenarios
6.1 Scenario 1: Successful Appointment Booking
// Setup
AppointmentScheduler scheduler = new AppointmentScheduler();
Doctor doctor = new Doctor("D001", "Dr. Smith", "Cardiology");
LocalDateTime requestTime = LocalDateTime.of(2025, 8, 31, 14, 0); // 2:00 PM
// Execution
String result = scheduler.bookAppointment(
"P001", // Patient ID
doctor, // Doctor object
requestTime, // Requested time
"Chest pain", // Reason
"Follow-up needed" // Doctor notes
);
// Expected Result
assertEquals("Appointment booked successfully!", result);
6.2 Scenario 2: Conflict Detection
// Setup - Existing appointment at 2:00 PM
Appointment existing = new Appointment("P002", "D001",
LocalDateTime.of(2025, 8, 31, 14, 0), "Regular checkup");
// Assume existing appointment is already in database
// Attempt to book at 2:15 PM (within 30-minute window)
String result = scheduler.bookAppointment(
"P003", doctor,
LocalDateTime.of(2025, 8, 31, 14, 15),
"Consultation", ""
);
// Expected Result
assertTrue(result.contains("conflicts with an existing appointment"));
6.3 Scenario 3: Multiple Doctor Coordination
// Different doctors can have appointments at the same time
Doctor doctor1 = new Doctor("D001", "Dr. Smith", "Cardiology");
Doctor doctor2 = new Doctor("D002", "Dr. Jones", "Neurology");
LocalDateTime sameTime = LocalDateTime.of(2025, 8, 31, 14, 0);
// Both should succeed - no conflict between different doctors
String result1 = scheduler.bookAppointment("P001", doctor1, sameTime, "Reason1", "");
String result2 = scheduler.bookAppointment("P002", doctor2, sameTime, "Reason2", "");
assertEquals("Appointment booked successfully!", result1);
assertEquals("Appointment booked successfully!", result2);
6.4 Scenario 4: Edge Case - Exact 30-Minute Boundary
// Existing appointment at 2:00 PM
LocalDateTime existingTime = LocalDateTime.of(2025, 8, 31, 14, 0);
// New appointment at 2:30 PM (exactly 30 minutes later)
LocalDateTime newTime = LocalDateTime.of(2025, 8, 31, 14, 30);
String result = scheduler.bookAppointment("P001", doctor, newTime, "Checkup", "");
// Should succeed - exactly 30 minutes is allowed
assertEquals("Appointment booked successfully!", result);
7. Benefits & Trade-offs
7.1 Mediator Pattern Benefits
Reduced Coupling
- Colleague objects don't need direct references to each other
- AppointmentController doesn't need to know about conflict detection logic
- Doctor objects don't manage their own scheduling conflicts
- Database operations are centralized and abstracted
Centralized Control
- All scheduling business rules in one location
- Consistent conflict detection across the application
- Easy to modify scheduling algorithms without affecting multiple classes
- Single point of control for appointment workflow
Simplified Object Protocols
- Complex many-to-many interactions become one-to-many with mediator
- Clear separation of concerns between UI, business logic, and data access
- Standardized communication patterns through mediator interface
Enhanced Maintainability
- Business rule changes require modifications in only one place
- Easy to add new scheduling constraints or validation rules
- Centralized error handling and logging
- Clear audit trail for all scheduling operations
7.2 Specific Healthcare Domain Benefits
Patient Safety
- Consistent conflict detection prevents double-booking
- Centralized validation ensures all business rules are applied
- Audit trail for all scheduling decisions
Operational Efficiency
- Streamlined booking process through centralized coordination
- Consistent error messages and user feedback
- Scalable approach for complex scheduling scenarios
System Integration
- Clean integration points with external systems
- Centralized database transaction management
- Consistent data validation and persistence
7.3 Trade-offs and Considerations
Mediator Complexity
- The mediator can become complex as it centralizes all coordination logic
- Risk of creating a "god object" if not properly designed
- Need to balance centralization with appropriate delegation
Performance Considerations
- Additional layer of indirection may impact performance
- Database queries centralized through mediator may need optimization
- Caching strategies may be needed for frequently accessed data
Testing Complexity
- Mediator requires comprehensive testing of all interaction scenarios
- Mock objects needed for testing isolated mediator behavior
- Integration testing becomes critical for validating complete workflows
Single Point of Failure
- Mediator becomes critical component - failures affect entire scheduling system
- Need for robust error handling and recovery mechanisms
- Monitoring and alerting for mediator health becomes important
8. Testing & Validation
8.1 Unit Testing the Mediator
@Test
public void testSuccessfulAppointmentBooking() {
// Arrange
AppointmentScheduler scheduler = new AppointmentScheduler();
Doctor doctor = new Doctor("D001", "Dr. Test", "General");
LocalDateTime appointmentTime = LocalDateTime.of(2025, 9, 1, 10, 0);
// Act
String result = scheduler.bookAppointment(
"P001", doctor, appointmentTime, "Check-up", "Regular visit"
);
// Assert
assertEquals("Appointment booked successfully!", result);
}
@Test
public void testConflictDetection() {
// Arrange - Create existing appointment
AppointmentScheduler scheduler = new AppointmentScheduler();
Doctor doctor = new Doctor("D001", "Dr. Test", "General");
LocalDateTime existingTime = LocalDateTime.of(2025, 9, 1, 10, 0);
LocalDateTime conflictTime = LocalDateTime.of(2025, 9, 1, 10, 15);
// Create first appointment
scheduler.bookAppointment("P001", doctor, existingTime, "First", "");
// Act - Try to book conflicting appointment
String result = scheduler.bookAppointment("P002", doctor, conflictTime, "Second", "");
// Assert
assertTrue(result.contains("conflicts with an existing appointment"));
}
@Test
public void testBoundaryConditions() {
AppointmentScheduler scheduler = new AppointmentScheduler();
Doctor doctor = new Doctor("D001", "Dr. Test", "General");
// Test exact 30-minute boundary
LocalDateTime baseTime = LocalDateTime.of(2025, 9, 1, 10, 0);
LocalDateTime boundaryTime = baseTime.plusMinutes(30);
scheduler.bookAppointment("P001", doctor, baseTime, "First", "");
String result = scheduler.bookAppointment("P002", doctor, boundaryTime, "Second", "");
assertEquals("Appointment booked successfully!", result);
}
8.2 Integration Testing
@Test
public void testCompleteSchedulingWorkflow() {
// Test complete workflow from controller through mediator to database
AppointmentController controller = new AppointmentController(view, mainFrame, user);
// Simulate user input
view.patientIdField.setText("P001");
view.reasonField.setText("Annual checkup");
// Simulate doctor selection
Doctor testDoctor = new Doctor("D001", "Dr. Test", "General");
view.selectDoctor(testDoctor);
// Simulate date/time selection
view.setDateTime(LocalDateTime.of(2025, 9, 1, 14, 0));
// Execute booking
controller.bookNewAppointment();
// Verify appointment was created in database
List<Appointment> appointments = dao.getAppointmentsForDoctorOnDate(
"D001", LocalDate.of(2025, 9, 1)
);
assertEquals(1, appointments.size());
assertEquals("P001", appointments.get(0).getPatientId());
}
8.3 Performance Testing
@Test
public void testConcurrentBookingScenarios() {
AppointmentScheduler scheduler = new AppointmentScheduler();
Doctor doctor = new Doctor("D001", "Dr. Test", "General");
ExecutorService executor = Executors.newFixedThreadPool(10);
List<Future<String>> futures = new ArrayList<>();
// Submit multiple concurrent booking requests
for (int i = 0; i < 10; i++) {
final int patientNum = i;
Future<String> future = executor.submit(() -> {
return scheduler.bookAppointment(
"P" + String.format("%03d", patientNum),
doctor,
LocalDateTime.of(2025, 9, 1, 10, patientNum * 5), // 5-minute intervals
"Test appointment " + patientNum,
""
);
});
futures.add(future);
}
// Verify all bookings completed successfully
for (Future<String> future : futures) {
String result = future.get();
assertEquals("Appointment booked successfully!", result);
}
}
8.4 Error Handling Testing
@Test
public void testDatabaseFailureHandling() {
// Test mediator behavior when database is unavailable
AppointmentScheduler scheduler = new AppointmentScheduler();
// Simulate database failure by using invalid connection
// This would require dependency injection or mocking for proper testing
String result = scheduler.bookAppointment(
"P001", doctor, appointmentTime, "Test", ""
);
assertEquals("Booking failed: Could not save to database.", result);
}
Conclusion
The Mediator pattern implementation in the appointment scheduling system demonstrates a sophisticated approach to managing complex interactions in healthcare software. The AppointmentScheduler class effectively centralizes scheduling logic while maintaining loose coupling between system components.
Key Achievements
- Complexity Management: Successfully encapsulates complex scheduling business rules in a single, manageable component
- Conflict Resolution: Implements robust 30-minute conflict detection algorithm with clear boundary conditions
- Integration Coordination: Seamlessly coordinates between UI controllers, domain objects, and data access layers
- Maintainability: Provides a single point of control for scheduling modifications and enhancements
- Healthcare Compliance: Ensures consistent application of scheduling rules critical for patient care coordination
Real-World Impact
The mediator pattern proves particularly valuable in healthcare contexts where:
- Patient Safety depends on accurate scheduling without conflicts
- Operational Efficiency requires streamlined booking processes
- System Integration needs clean interfaces between complex subsystems
- Regulatory Compliance demands consistent business rule application
The implementation successfully addresses the challenge of managing complex interactions while maintaining clean architecture principles and providing a foundation for future enhancements to the scheduling system.
Document Status: Part B Complete
Next: Part C - Billing & Insurance Claims (Chain of Responsibility Pattern)