Early Access: 87 spots left.

Claim
Low Level DesignSingle ResponsibilitySingle Responsibility: Refactor UserService

Course content

Single Responsibility: Refactor UserService

Medium·Tagssolidsingle-responsibilityrefactoring

Problem Statement

The starter `UserService` mixes three responsibilities into a single class: 1. Persistence: saving and retrieving users from an in-memory "database". 2. Notification: sending welcome emails, printed to stdout for this exercise. 3. Report formatting: rendering an HTML view of a user. Refactor the design so that each concern lives in its own class. After your refactor, the design must expose this exact public API. The validator will reflect on it. ```java class UserRepository { public void save(String userId, String email, String name); public java.util.Map<String, Object> findById(String userId); } class EmailNotifier { public void sendWelcome(String email, String name); // prints to stdout } class UserReportFormatter { public String renderHtml(String name, String email); } class UserService { public UserService(UserRepository repo, EmailNotifier notifier); public void registerUser(String userId, String email, String name); // delegates: persist via repo, then notify via notifier. } ``` Note that `UserReportFormatter` is not a dependency of `UserService`. Formatting a report is a different concern from registering a user, and keeping them separate is part of what this exercise teaches. The validator runs five checks: 1. `UserRepository` works in isolation. The validator instantiates it and verifies that `save` followed by `findById` round-trips correctly. 2. `EmailNotifier` works in isolation. The validator captures stdout from `sendWelcome` and verifies that the recipient and name appear. 3. `UserReportFormatter` works in isolation. The validator calls `renderHtml` and verifies that the inputs are reflected in the output. 4. `UserService` delegates correctly. The validator constructs `UserService(repo, notifier)`, calls `registerUser`, and then asserts that the repo persisted the user and that stdout contains the welcome email. 5. Single responsibility. The validator uses reflection to assert that each class declares only methods for its own concern. For example, `UserRepository` must not declare a `sendWelcome` method, and `EmailNotifier` must not declare a `save` method. If you keep everything in `UserService`, every test fails. The other classes remain empty stubs, so the validator cannot construct them in isolation. Starter code and validators are available in Java, Python, and C++.

Examples

Example 1
Input
UserRepository repo = new UserRepository(); EmailNotifier n = new EmailNotifier(); UserService svc = new UserService(repo, n); svc.registerUser("u1", "alice@example.com", "Alice"); repo.findById("u1")
Output
"{name=\"Alice\", email=\"alice@example.com\"}"
Why
After registration, the repository holds the new user and the notifier has printed a welcome message to stdout.
Example 2
Input
new UserReportFormatter().renderHtml("Alice", "alice@example.com")
Output
"\"<div class=\\\"user\\\"><p>Name: Alice</p><p>Email: alice@example.com</p></div>\""
Why
The formatter is independent. It does not touch persistence or notification.

Constraints

  • Refactor must produce exactly four top-level classes: UserService, UserRepository, EmailNotifier, UserReportFormatter.
  • UserService's only public methods are the constructor and registerUser.
  • UserService must not declare its own save / findById / sendWelcome / renderHtml methods.
  • EmailNotifier prints the welcome message to stdout (System.out).

Hints

Stuck? Reveal a nudge toward the right pattern, one step at a time.

Hint 1
Move the persistence code (the static `DB` map and the put/get calls) out of UserService and into UserRepository. UserService should no longer hold any state of its own.
Hint 2
EmailNotifier.sendWelcome must print to System.out using the same format that the smelly starter used, for example `[email to alice@example.com] Hi Alice, welcome to our app!`. The validator captures stdout and checks for the email and name.
Hint 3
Add a UserService constructor that takes UserRepository and EmailNotifier, store them in private final fields, and have registerUser delegate to them.
Hint 4
UserReportFormatter is not a dependency of UserService. The validator constructs the formatter on its own, so your refactor does not need to wire it into the orchestrator.
Hint 5
The reflection check looks at declared methods, so private helpers are fine. What it forbids is having, for example, a `sendWelcome` method on UserRepository. Keep each class's API focused on one concern.