Mi pregunta es sobre cuál debería ser la solución más orientada a objetos y el patrón de diseño adecuado para mi situación. Tenemos una entidad de usuario y varias entidades de cuenta pertenecen al usuario. Las entidades de cuenta pueden tener múltiples estados y podemos ejecutar múltiples operaciones en cuentas. El resultado de estas operaciones se basa en el estado de la entidad de cuenta.
Tengo el siguiente código que se basa principalmente en el cambio (a veces se ve como algunos "si"). Me gustaría cambiarlo pero no puedo encontrar el patrón de diseño correcto.
enum Status { ACTIVE, INACTIVE, DELETED; } @Entity class Account { private long id; private long userid; private Status status; //... } class AccountService{ Account delete(long id) { //... if (accountInfo.getSatus() == DELETED) { throw new IllegalOperationException(); } if (accountInfo.getStatus() == ACTIVE || accountInfo.getStatus()) { accountInfo.setStatus(DELETED); accountInfoRepository.save(accountInfo); } } Account create (Account account) { // various operations based on state } }
Tengo muchas ganas de refactorizar estos códigos, me temo que tan pronto como nuestro servicio crezca, contendrá más "magia" y será difícil de mantener. Y si quisiéramos introducir un nuevo estado, sería casi imposible.
Mi mente junior pensó que debería tener objetos de estado que implementarían todas las operaciones, en estilo pseudocódigo:
class AccountService { private StateFactory stateFactory; private AccountRepository accountRepository; Account delete(long id) { final Optional<Account> account = accountRepository.findById(id); Account deletedAccount = account.map(stateFactory::getByState) .map(accountState -> accountState.delete(account)) .orElseThrow(() -> new IllegalOperationException()); return accountRepository.save(deletedAccount); } Account create (Account account) { // various operation based on state } }
y:
class ActiveState extends AccountState { @Override public Account delete(Account account) { //implementation } @Override public Account activate(AccountInfo) { // implementation } }
y:
interface AccountState { Account activate(AccountInfo); Account delete(AccountInfo); }
Sé que debe haber una mejor implementación para este problema. ¿Qué otros patrones de diseño son adecuados para esta configuración?
ACTUALIZAR
He encontrado algunos artículos interesantes para leer en el tema:
Cómo implementar una FSM - Máquina de estados finitos en Java
Si entendí la pregunta correctamente, entonces es necesario aplicar alguna acción por su estado. Si es cierto, entonces podemos usar el patrón de fábrica para obtener el objeto deseado que puede ejecutar alguna acción. El mapeo entre el estado y la acción se puede poner en HashTable
.
Entonces, veamos un ejemplo de código. Escribiré a través de C#, pero este código se puede traducir fácilmente a Java porque los idiomas tienen muchas similitudes de sintaxis.
Entonces tendremos una enumeración de estados:
public enum Status { Active, Deleted, Foo }
y estados de AccountState
public abstract class AccountState { public abstract void ExecSomeLogic(); } public class ActiveState : AccountState // "extends" instead of ":" in Java { public override void ExecSomeLogic() { } } public class DeletedState : AccountState // "extends" instead of ":" in Java { public override void ExecSomeLogic() { } } public class FooState : AccountState // "extends" instead of ":" in Java { public override void ExecSomeLogic() { } }
Luego necesitamos la clase de mapeador de Status
a su AccountState
:
public class StatusToAccountState { public Dictionary<Status, AccountState> AccountStateByStatus { get; set; } = new Dictionary<Status, AccountState>() // HashMap in Java { { Status.Active, new ActiveState() }, { Status.Deleted, new DeletedState() }, { Status.Foo, new FooState() }, }; }
Y luego en su servicio puede usarlo así:
void Delete(long id, Status status) { StatusToAccountState statusToAccountState = new StatusToAccountState(); AccountState accountState = statusToAccountState.AccountStateByStatus[status]; accountState.ExecSomeLogic(); }
Si hay mucha lógica para averiguar cuál es el Status
del objeto, entonces puede crear alguna clase que tenga solo una responsabilidad de averiguar qué estado del objeto es:
public class StatusManager { public Status Get() { return Status.Active; // or something another based on logic } }
Después de hacer esto, sus clases se corresponderán con el principio de responsabilidad única de SOLID. Lea más sobre el principio de responsabilidad única de SOLID aquí
Demasiadas sentencias switch-/if indican que el código huele a "Abusadores de herramientas" (ver M. Fowler "Refactorización"). Usa la mecánica del polimorfismo para resolver esto. https://refactoring.guru/smells/switch-statements