@AllArgsConstructor
public class RegistrablePersonRegisteredPerson implements Person {
private Long id;
private String name;
private final PersonRegistry registry;
public RegistrablePersonRegisteredPerson(String name, PersonRegistry registry) {
this.name = name;
this.registry = registry;
}
public void register() {
if (id != null) {
throw new PersonAlreadyRegisteredException(name);
}
id = registry.register(name);
}
public void changeName(String name) {
this.name = name;
if (id != null) {
registry.changeName(id, name);
}
}
}
@RequiredArgsConstructor
public class RegistrablePersonsRegisteredPersons implements Persons {
private final PersonRegistry registry;
public Person register(String name) {
if (registry.byName(name).isPresent()) {
throw new PersonAlreadyRegisteredException(name);
}
Person person = new RegistrablePersonRegisteredPerson(name, registry);
person.register();
return person;
}
public Person findByName(String name) {
return registry.byName(name)
.orElseThrow(() -> new PersonNotFoundException(name));
}
}
interface PersonRegistry {
long register(String name);
Optional<Person> byName(String name);
void changeName(long id, String name);
}
class InMemoryPersonRegistry implements PersonRegistry {
private final Map<Long, PersonEntry> personEntries = new HashMap<>();
private final AtomicLong idSequence = new AtomicLong();
public long register(String name) {
long id = idSequence.incrementAndGet();
personEntries.put(id, new PersonEntry(id, name));
return id;
}
public Optional<Person> byName(String name) {
return personEntries.values().stream()
.filter(entry -> name.equals(entry.name))
.findAny()
.map(entry -> new RegistrablePersonRegisteredPerson(
entry.id, entry.username, this
));
}
public void changeName(long id, String name) {
if (personEntries.containsKey(id)) {
personEntries.get(id).name = name;
}
}
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
private class PersonEntry {
public long id;
public String name;
}
}
What I don't like about this solution it the coupling between InMemoryPersonRegistry and RegistrablePersonRegisteredPerson. For example, In a situation when the RegistrablePersonRegisteredPerson has more dependencies, all of them must be known to the InMemoryPersonRegistry. Possible solution for this would be to introduce a factory and make the repository (registry) dependent only on that factory. But the coupling still doesn't feel alright.
public class RegistrablePersonRegisteredPerson implements Person {
private final PersonEntries entries;
/* ... */
public void register() {
if (id != null) {
throw new PersonAlreadyRegisteredException(name);
}
id = entries.save(new PersonEntries.PersonEntry(null, name));
}
public void changeName(String name) {
this.name = name;
if (id != null) {
entries.updateName(id, name);
}
}
}
public class RegistrablePersonsRegisteredPersons implements Persons {
private final PersonEntries entries;
/* ... */
public Person findByName(String name) {
return entries.byName(name)
.map(entry -> new RegistrablePersonRegisteredPerson(entry.id, entry.name, entries))
.orElseThrow(() -> new PersonNotFoundException(name));
}
}
// RegistrablePersonRegisteredPerson.register():
PersonEntries.PersonEntry entry = new PersonEntries.PersonEntry(null, name);
entries.save(entry);
this.id = entry.id;
EDIT
According the Torben's comment changed RegisteredPerson => RegistrablePerson