I know there are similar questions already, but looking at them I still have some doubts about how I should design my code. I have a service that allows for User registration / login /update / delete. The thing is that the User is an abstract type, which contains the data typeOfUser based on which the actual registration / update / delete methods should be called, and right now I do that in a switch-case block. I'd like to replace that with some better design.
UserController.java
public class UserController {
public UserDto register(UserDto user) {
switch(user.getTypeOfUser()) {
case DRIVER: return driverService.register(user);
case CUSTOMER: return customerService.register(user);
// ...
}
}
public UserDto update(UserDto user) {
switch(user.getTypeOfUser) {
case DRIVER: return driverService.update((DriverDto) user);
case CUSTOMER: return customerService.update((CustomerDto) user);
// ...
}
}
public UserDto login(long userId) {
loginService.login(userId);
UserBO user = userService.readById(userId);
switch(user.getTypeOfUser) {
case DRIVER: return DriverDto.fromBO((DriverBO) user);
case CUSTOMER: return CustomerDto.fromBO((CustomerBO) user);
// ...
}
}
// ...
}
I understand that something like Visitor pattern could be used, but would I really need to add the methods of registration / login /update / delete in the Enum itself? I don't really have a clear idea on how to do that, any help is appreciated.
I'd like to replace that with some better design.
The first step towards replacing the switch statement and take advantage of Polymorphism instead is to ensure that there is a single contract (read method signature) for each of the operations regardless of the user type. The following steps will explain how to achieve this :
Step 1 : Define a common interface for performing all operations
interface UserService {
public UserDto register(UserDto user);
public UserDto update(UserDto user);
public UserDto login(UserDto user)
}
Step 2 : Make UserController take a UserService as a dependency
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
public UserDto register(UserDto user) {
userService.register(user);
}
public UserDto update(UserDto user) {
userService.update(user);
}
public UserDto login(long userId) {
userService.login(user);
}
}
Step 3 : Create subclasses to handle different types of users that take CustomerDto and CustomerBO as a dependency
class CustomerService implements UserService {
private CustomerDto userDto;
private CustomerBO userBO;
public CustomerService(UserDto userDto,UserBO userBo) {
this.userDto = (CustomerDto)userDto;
this.userBO= (CustomerBO)userBo;
}
//implement register,login and update methods to operate on userDto and userBo
}
Implement the DriverService class in a similar fashion with a dependency on DriverBo and DriverDto objects respectively.
Step 4 : Implement a runtime factory that decides which service to pass to UserController :
public UserControllerFactory {
public static void createUserController(UserDto user) {
if(user.getTypeOfUser().equals(CUSTOMER)) {
return new UserController(new CustomerService(user));
} else if(user.getTypeOfUser().equals(DRIVER)) {
return new UserController(new DriverService(user));
}
}
}
Step 5 Call the factory to create a user controller
UserDto user = someMethodThatCreatesUserDto(();
UserController controller = UserControllerFactory.createUserController(user);
controller.register();
controller.update();
controller.login();
The advantage of the above approach is that the switch/if-else statements are moved all they way back to a single class i.e the factory.
You'd want something like that:
public abstract class User {
abstract void register();
abstract void update();
abstract void login();
// maybe some more common non-abstract methods
}
Any type of User will have a class that extends this abstract class and therefore must implement all its abstract methods, like this:
public class Driver extends User {
public void register() {
// do whatever a driver does when register...
}
public void update() {
// do whatever a driver does when update...
}
public void login() {
// do whatever a driver does when login...
}
}
public class Customer extends User {
public void register() {
// do whatever a customer does when register...
}
public void update() {
// do whatever a customer does when update...
}
public void login() {
// do whatever a customer does when login...
}
}
This way, you're avoiding any switch case code. For instance, you can have an array of Users, each one them will be instantiated using new Driver() or new Customer(). Then, for example, if you're iterating over this array and executing all the Users login() method, each user's login() will be called according to its specific type ==> no switch-case needed, no casting needed!
Very simple example (only for different login logic for DriverDto and CustomerDto) - I've resigned from field typeOfUser (because it is not necessary in my solution) - I'm not sure that this is possible in your solution:
public abstract class UserDto {
// put some generic data & methods here
}
public class CustomerDto extends UserDto {
private String customerName;
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
}
public class DriverDto extends UserDto {
private String driverName;
public String getDriverName() {
return driverName;
}
public void setDriverName(String driverName) {
this.driverName = driverName;
}
}
public class ThisIsServiceOrDelegateToOtherServices {
public void login(CustomerDto customer) {
String name = customer.getCustomerName();
System.out.println(name);
// work on name here
}
public void login(DriverDto customer) {
String name = customer.getDriverName();
System.out.println(name);
// work on name here
}
}
Usage:
public static void main(String... args) {
//demo data
CustomerDto customer = new CustomerDto();
customer.setCustomerName("customerName");
DriverDto driver = new DriverDto();
driver.setDriverName("driverName");
// usage
ThisIsServiceOrDelegateToOtherServices service = new ThisIsServiceOrDelegateToOtherServices();
service.login(customer);
service.login(driver);
}
If you really need that TypeOfUser-enum in your UserDTO, then you could extend your enum with a serivce. So you create a TypeOfUserService interface. CustomerSerivce and DriverService will inherit from that service:
public interface TypeOfUserService {
public void register(UserDTO user);
// ...
}
public class CustomerService implements TypeOfUserService {
#Override
public void register(UserDTO user) {
// ...
}
}
public class DriverService implements TypeOfUserService {
#Override
public void register(UserDTO user) {
// ...
}
}
Then you create your register, update, etc. methods in your TypeOfUser enum:
public enum TypeOfUser {
DRIVER(new DriverService()),
CUSTOMER(new CustomerService());
private TypeOfUserService typeOfUserService;
TypeOfUser(TypeOfUserService typeOfUserService) {
this.typeOfUserService = typeOfUserService;
}
public static void register(String typeOfUser, UserDTO user) {
TypeOfUser.valueOf(typeOfUser).typeOfUserService.register(user);
}
// ...
}
You could then call the register method via:
class UserController() {
public UserDto register(UserDto user) {
TypeOfUser.register(user.getTypeOfUser, user);
}
}
Related
I have a quite simple quarkus extension which defines a ContainerRequestFilter to filter authentication and add data to a custom AuthenticationContext.
Here is my code:
runtime/AuthenticationContext.java
public interface AuthenticationContext {
User getCurrentUser();
}
runtime/AuthenticationContextImpl.java
#RequestScoped
public class AuthenticationContextImpl implements AuthenticationContext {
private User user;
#Override
public User getCurrentUser() {
return user;
}
public void setCurrentUser(User user) {
this.user = user;
}
}
runtime/MyFilter.java
#ApplicationScoped
public class MyFilter implements ContainerRequestFilter {
#Inject
AuthenticationContextImpl authCtx;
#Override
public void filter(ContainerRequestContext requestContext){
// doing some stuff like retrieving the user from the request Context
// ...
authCtx.setCurrentUser(retrievedUser)
}
}
deployment/MyProcessor.java:
class MyProcessor {
//... Some stuff
#BuildStep
AdditionalBeanBuildItem createContext() {
return new AdditionalBeanBuildItem(AuthenticationContextImpl.class);
}
}
I have a Null Pointer Exception in authCtx.setCurrentUser(retrievedUser) call (authCtx is never injected)
What am I missing here ?
Thanks
Indexing the runtime module of the extension fixes the problem.
There are multiple ways to do that as mentioned in https://stackoverflow.com/a/55513723/2504224
I would like to create an abstract factory. here is what I tried.
//abstract class Worker
public abstract class Worker {
String phoneNumber;
String firstName;
String lastName;
String workerType;
String ifu;
String imageParth;
//....
public String getWorkerType() {
return workerType;
}
}
// Electrician class which extends worker
package worker.domain.worker;
public class Electrician extends Worker{
public Electrician() {}
public Electrician(String phoneNumber, String firstName, String lastName, String ifu, String workerType,
String imageParth) {
super(phoneNumber, firstName, lastName, ifu,workerType, imageParth);
}
public String getWorkerType() {
return "Electrician";
}
}
//Mason class
package worker.domaine.worker;
public class Mason extends Worker{
public Mason() {};
public Mason(String phoneNumber, String firstName, String lastName, String ifu,String workerType,
String imageParth) {
super(phoneNumber, firstName, lastName, ifu, workerType, imageParth);
}
String getworkerType() {
return "Mason";
}
}
// interface WorkerAbstractFactory
package worker.domaine.worker;
public interface WorkerAbstractFactory {
Worker createWorker(String typeWorker);
}
//
public class WorkerFactory implements WorkerAbstractFactory{
#Override
public Worker createWorker(String typeWorker) {
Worker worker = null;
if(worker != null) {
switch (typeWorker) {
case "Electrician":
Electrician electrician =new Electrician();
electrician = new Electrician (electrician.getPhoneNumber(), electrician.getFirstName(), electrician.getLastName(), electrician.getIfu(), electrician.getWorkerType(),electrician.getImageParth());
case "Mason":
Mason mason =new Mason();
mason = new Mason (mason.getPhoneNumber(), mason.getFirstName(), mason.getLastName(), mason.getIfu(), mason.getworkerType(),mason.getImageParth());
}}
//app class
public class WorkerFactoryProvider {
public static WorkerAbstractFactory getWorkerFactory(String workerCategory) {
//WorkerFactory workerFactory = new WorkerFactory();
WorkerFactory workerFactory = new WorkerFactory();
if (workerCategory != null) {
switch (workerCategory) {
case "Electrician":
Worker worker1 = workerFactory.createWorker("Electrician");
worker1.getWorkerType();
String a=worker1.getWorkerType();
System.out.println(a);
case "Mason":
Worker worker2 = workerFactory.createWorker("Mason");
worker2.getWorkerType();
String b=worker2.getWorkerType();
System.out.println(b);
}
}
return null;
}
do you think it could work like that? now, if I really want a concrete object, how could it be done? because I would like to write for example a method to calculate the pay of each worker according to type for example how could I use my abstract Factory in the method to return me each type.
You have a single class hierarchy of Worker types. To instantiate those you can just use a standalone factory class, you don't need an abstract factory here. For example this would be sufficient:
public class WorkerFactory {
public Worker createWorker(String workerType) {
switch (workerType) {
case "Electrician": return new Electrician();
case "Mason": return new Mason();
}
}
}
The abstract factory pattern is more elaborate, and allows injecting different concrete factories for related hierarchies of objects, so that the client doesn't need to be aware of the difference. For example you could have an abstract TransportationFactory:
interface Transportation {
void travelTo(String destination);
}
interface TransportationFactory {
Transportation simple();
Transportation luxurious();
}
And two concrete implementations (matching two different but similar class hierarchies):
class WaterTransportationFactory {
Transportation simple() {
return new Kayak();
}
Transportation luxurious() {
return new Yacht();
}
}
And:
class LandTransportationFactory {
Transportation simple() {
return new Bike();
}
Transportation luxurious() {
return new RaceCar();
}
}
The benefit of this pattern is that the client can be configured to use water or land transportation (or a new air transportation that is added later) without the need to undergo any changes:
class Client {
private TransportationFactory transportationFactory;
public Client(TransportationFactory transportationFactory) {
this.transportationFactory = transportationFactory;
}
public void travel(String destination) {
transportationFactory.simple().travelTo(destination);
}
public void travelInStyle(String destination) {
transportationFactory.luxurious().travelTo(destination);
}
}
EDIT: You could change the simple/luxurious methods to match the style of your example with the getWorkerType method. I prefer to avoid the conditional logic if possible and let the created classes determine their availability themselves. This decouples even further, allowing hierarchy members to be added with minimal code changes:
enum TransportationType {
SIMPLE, LUXURIOUS
}
interface Transportation {
void travelTo(String destination);
// allow the class to specify its own type
TransportationType getType();
}
// intermediate interface to distinguish Water from Land
interface WaterTransportation extends Transportation {
}
class Kayak implements WaterTransportation {
void travelTo(String destination) {
// splash splash
}
TransportationType getType() {
return TransportationType.SIMPLE;
}
}
class WaterTransportationFactory {
private WaterTransportation[] waterTransportations;
// Inject all available beans implementing WaterTransportation
// e.g. using Spring or some other dependency injection mechanism
public WaterTransportationFactory(WaterTransportation[] waterTransportations) {
this.waterTransportations = waterTransportations;
}
public Transportation create(TransportationType type) {
for(WaterTransportation waterTransportation : waterTransportations) {
if (waterTransportation.getType() == type) {
// we are returning the same instance every time
// this could be ok for singleton beans
// but if you really need a fresh instance you could use builders (see below)
return waterTransportation;
}
}
throw new IllegalArgumentException("No implementation for WaterTransportation type=" + type);
}
}
An alternative with builders:
KayakBuilder implements WaterTransportationBuilder {
KayakBuilder name(String name) { ... };
KayakBuilder weight(String weightInKg) { ... };
KayakBuilder year(String yearBuilt) { ... };
KayakBuilder speed(String averageSpeed) { ... };
Kayak build() { return kayak; }
}
For more on Builders see this full exposition of the Builder pattern
class WaterTransportationFactory {
private WaterTransportationBuilder[] builders;
// Inject all available WaterTransportationBuilders
// e.g. using Spring or some other dependency injection mechanism
public WaterTransportationFactory(WaterTransportationBuilder[] builders) {
this.builders = builders;
}
// extra arguments can be passed to build the instance
public Transportation create(TransportationType type, String name, int weightInKg, int yearBuilt, int averageSpeed) {
for(WaterTransportationBuilder builder: builders) {
if (builder.getType() == type) {
return builder
.name(name)
.weight(weightInKg)
.year(yearBuilt)
.speed(averageSpeed)
.build();
}
}
throw new IllegalArgumentException("No implementation for WaterTransportation type=" + type);
}
}
I want a factory class that return a service that I can use to do some validations. I implemented this class
public class EventUpdateValidatorFactory {
public EventUpdateValidatorStrategy getValidator(EEventStatus eventStatus) {
if (SECOND_APPROVAL.equals(eventStatus)) {
return new EventSecondApprovalValidator();
} else if (APPROVED.equals(eventStatus)) {
return new EventApprovedValidator();
} else if (ACCOUNTING_HQ.equals(eventStatus)) {
return new EventAccountingHqValidator();
}
throw new IllegalArgumentException("Unknown status");
}
}
The interface EventUpdateValidatorStrategy is this
public interface EventUpdateValidatorStrategy {
default <T extends EventUpdateValidatorStrategy> void validate(User user, EventMasterData masterData, Event event, List<EventExternalSystemExpenseSave> expenses,
List<EventExternalSystemSpeakerSave> speakers, long eventId) {
this.validateMasterData(masterData, event);
this.validateSpeakers(speakers, eventId);
this.validateExpenses(expenses, eventId);
this.doUpdate(user, masterData, expenses, speakers, eventId);
}
void validateMasterData(EventMasterData masterData, Event event);
void validateExpenses(List<EventExternalSystemExpenseSave> expenses, long eventId);
void validateSpeakers(List<EventExternalSystemSpeakerSave> speakers, long eventId);
void doUpdate(User user, EventMasterData masterData, List<EventExternalSystemExpenseSave> expenses, List<EventExternalSystemSpeakerSave> speakers, long eventId);
}
The EventSecondApprovalValidator is this
#Service
#Transactional
public class EventSecondApprovalValidator implements EventUpdateValidatorStrategy {
#Autowired
private EventService eventService;
#Autowired
private ContextDateService contextDateService;
#Autowired
private EventExpenseService eventExpenseService;
#Autowired
private EventExternalSystemDAO eventExternalSystemDAO;
#Override
public void validateMasterData(LocalEventMasterData masterData, Event event) {
// some logic
}
#Override
public void validateExpenses(List<EventExternalSystemExpenseSave> expenses, long eventId) {
// some logic
}
#Override
public void validateSpeakers(List<EventExternalSystemSpeakerSave> speakers, long eventId) {
// some logic
}
#Override
public void doUpdate(User user, EventMasterData masterData, List<EventExternalSystemExpenseSave> expenses, List<EventExternalSystemSpeakerSave> speakers, long eventId) {
ofNullable(expenses).ifPresent(expensesToSave -> expensesToSave.forEach(expense -> this.eventExternalSystemDAO.updateExpense(user, expense)));
this.eventExternalSystemDAO.updateEvent(user, masterData, eventId);
}
}
The other EventApprovedValidator and EventAccountingHqValidator implementations are similar.
From main code I do this call
final EventUpdateValidatorStrategy validator = EventUpdateValidatorFactory.getValidator(event.getStatus());
validator.validate(user, eventSave.getMasterData(), event, eventSave.getExpenses(), eventSave.getSpeakers(), eventID);
and the result is that when I enter inside a EventSecondApprovalValidator all the autowired services are null and, obviously, I receive a NPE the first time that I use one of that service.
How I correctly use the factory to return the service that I need based on EEventStatus?
In EventUpdateValidatorFactory.getValidator(EEventStatus) method, you need to return the EventSecondApprovalValidator bean from context, instead of creating a new instance using new keyword.
The class EventSecondApprovalValidator is #Service annotated (and assuming there is only one of this type), an instance of this type will be added to ApplicationContext by Spring with all dependencies injected. So, just fetch it from context and use it.
One quick way to do this is as follows:
public EventUpdateValidatorStrategy getValidator(ApplicationContext context,
EEventStatus eventStatus) {
if (SECOND_APPROVAL.equals(eventStatus)) {
return context.getBean(EventSecondApprovalValidator.class);
} else if (APPROVED.equals(eventStatus)) {
return context.getBean(EventApprovedValidator.class);
} else if (ACCOUNTING_HQ.equals(eventStatus)) {
return context.getBean(EventAccountingHqValidator.class);
}
throw new IllegalArgumentException("Unknown status");
}
You can also #Autowire all validators in EventUpdateValidatorFactory and return the #Autowired instances. This will keep the getValidator method's signature same, but you'll have to make EventUpdateValidatorFactory a #Component-esque class.
#Component
public class EventUpdateValidatorFactory {
#Autowired
EventSecondApprovalValidator a;
#Autowired
EventApprovedValidator b;
#Autowired
EventAccountingHqValidator c;
public EventUpdateValidatorStrategy getValidator(EEventStatus eventStatus) {
if (SECOND_APPROVAL.equals(eventStatus)) {
return a;
} else if (APPROVED.equals(eventStatus)) {
return b;
} else if (ACCOUNTING_HQ.equals(eventStatus)) {
return c;
}
throw new IllegalArgumentException("Unknown status");
}
Creating an object manually you are not letting Spring perform autowiring. Consider managing your services by Spring as well.
#Component
public class MyServiceAdapter implements MyService {
#Autowired
private MyServiceOne myServiceOne;
#Autowired
private MyServiceTwo myServiceTwo;
#Autowired
private MyServiceThree myServiceThree;
#Autowired
private MyServiceDefault myServiceDefault;
public boolean checkStatus(String service) {
service = service.toLowerCase();
if (service.equals("one")) {
return myServiceOne.checkStatus();
} else if (service.equals("two")) {
return myServiceTwo.checkStatus();
} else if (service.equals("three")) {
return myServiceThree.checkStatus();
} else {
return myServiceDefault.checkStatus();
}
}
}
I have following classes:
class ServiceA{
User getUser(){} // API call to userServiceA.com
Profile getProfile(){} // API call to profileServiceA.com
}
class ServiceB{
User getUser(){} // API call to userServiceB.com
Profile getProfile(){} // API call to profileServiceB.com
}
class GroupService(){
ServiceA serviceA;
ServiceB serviceB;
constructor(){
this.serviceA = new ServiceA();
this.serviceB = new ServiceB();
}
getUser(String type){
if(type.equals("A")){
serviceA.getUser();
}else if(type.equals("B")){
serviceB.getUser();
}
}
}
class Controller(){
get(RC routingContext){
String type = routingContext.getParam("type");
GroupService groupService = new GroupService();
groupService.getUser(type);
}
}
In this project ServiceC,D,E... will be kept adding, and that will turn the GroupService class into a chaos.
For this particular scenario, which is the correct design pattern I can apply? Factory pattern is one solution, but then if else or Map will still exist.
Update: Using Strategy Pattern
public interface ServiceAZ {
User getUser();
Profile getProfile();
}
class ServiceA implements ServiceAZ{
#Override
public User getUser() {
return // API call to userServiceA.com
}
#Override
public Profile getProfile() {
return // API call to profileServiceA.com
}
}
class ServiceB implements ServiceAZ{
#Override
public User getUser() {
return // API call to userServiceB.com
}
#Override
public Profile getProfile() {
return // API call to profileServiceB.com
}
}
class GroupService(){
private ServiceAZ service;
GroupService(ServiceAZ s){
this.service = s;
}
User getUser(){
service.getUser();
}
}
class Controller(){
get(routingContext){
String type = routingContext.getParam("type");
ServiceAZ service;
if(type.equals("A")){
service = new ServiceA();
}
if(type.equals("B")){
service = new ServiceB();
}
GroupService groupService = new GroupService(service);
groupService.getUser(type);
}
}
This looks much better but now in the controller, the if else is a problem
I had in my class AbstractJpaDao method
#Override
public EntityManager getEntityManager() {
return em;
}
now it isn't in use and I wanted to delete it, but i get error:
The type JpaAclIdentityDao must implement the inherited abstract method IJpaDao.getEntityManager() in class JpaAclIdentityDao.
is that getter necessary? if not how to remove it
my code:
public abstract class AbstractJpaDao implements IJpaDao {
protected final IApplicationConfig config;
protected final EntityManager em;
private final SingletonEventBus eventBus;
public AbstractJpaDao(EntityManager entityManager, IApplicationConfig config, SingletonEventBus eventBus) {
checkArgument(entityManager != null);
checkArgument(config != null);
checkArgument(eventBus != null);
this.em = entityManager;
this.config = config;
this.eventBus = eventBus;
}
protected void saveEntity(IEntity entity) {
boolean isNew = entity.getId() == 0;
em.getTransaction().begin();
try {
em.persist(entity);
em.getTransaction().commit();
if (isNew) {
eventBus.post(new EntityCreatedEvent(entity));
}
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
}
#Repository
public class JpaAclIdentityDao extends AbstractJpaDao implements IAclIdentityDao {
public static final String GROUP_NAME_PATTERN = "GROUP_%d";
private static final String GROUP_TEMP_NAME = "TEMP_GROUP_NAME";
#Inject
public JpaAclIdentityDao(EntityManager entityManager, IApplicationConfig config, SingletonEventBus eventBus) {
super(entityManager, config, eventBus);
}
#Override
public AclIdentity findById(Object id) throws EntityNotFoundException {
return em.find(AclIdentity.class, id);
}
#Override
public List<AclIdentity> findAll() {
return findAllByType(AclIdentity.class);
}
#Override
public void delete(AclIdentity entity) {
// TODO Auto-generated method stub
}
#Override
public void save(AclIdentity entity) {
saveEntity(entity);
}
#Override
public AclIdentity createNew(String sid, boolean principal) {
AclIdentity identity = new AclIdentity(sid, principal);
save(identity);
return identity;
}
#Override
public AclIdentity createNew(User entity) {
return createNew(entity.getEmail(), true);
}
#Override
public AclIdentity createNew(Group entity) {
AclIdentity identity = createNew(GROUP_TEMP_NAME, false);
identity.setSid(String.format(GROUP_NAME_PATTERN, identity.getId()));
save(identity);
return identity;
}
}
Yes, you have to implement all methodes which are defined in the implemented interface. The only possible solutions i can think of, is to implement the method and leave it empty, or don't implement the interface.
Or, as ben75 said, just remove the method in the declaration of your interface "IJpaDao" if you don't need it (anywhere).
The method getEntityManager is defined in IJpaDao (or one super interface) that's why you need to provide an implementation of it in your class.
If it is not use at all (i.e. even by some reflection mechanism inside some frameworks you are using), then you can remove it from IJpaDao and you won't be forced to implement it.
If you don't want to use it, then throw an UnsupportedOperationException:
public class JpaAclIdentityDao extends AbstractJpaDao ... { // Or AbstractJpaDao...
// Some Code...
public EntityManager getEntityManager() {
throw new UnsupportedOperationException();
// return null; (This is not needed, due to the exception thrown above. Thanks dedek!)
}
// More Code...
}
Due to OO programing, if a concrete class inherits a class/interface with an abstract method, you must define that method, or make your class abstract and pass it down the line, like you did with AbstractJpaDao.
I am guessing that the Interface IJpaDao contains a getEntityManager abstract method.