We have a desktop Swing application. It executes operations against a DB. It could be plain DML, it could be procedures. In both cases, it could result in an error. In that case, we need to display a dialog window to the user
I want to do it through a common reusable abstraction that I can easily mock in unit tests
public interface Notifier {
// Notification is some data class: may contain status, summary, etc
void notify(Notification notification);
}
// some GUI panel
// I originally named it Report instead of Notification
Notification deleteNotification = service.delete(something);
// may display error dialog if it's an error
notifier.notify(deleteNotification);
But here's the problem. Our procedures return a DTO that, among other things, specifies what dialog we should show. And while the error/warn distinction poses no problem, the DTOs can also (rarely) mandate dialogs with the "Retry" option
And that changes everything
The Notifier abstraction doesn't care about (or requests) the user's response at all. It's a strictly one-way interaction. But because of those rare cases when further execution of the program depends on the user's choice, I now have to either
- Create two abstractions: one for handling errors of plain DML operations, another for handling procedure errors (because of those rare cases when user feedback is important)
- Or change the
Notifierabstraction to make it return someUserChoiceobject (could be a enum)
public interface Notifier {
UserChoice notify(Notification notification);
}
public enum UserChoice {
OK, NO, RETRY;
}
// some GUI panel
UserChoice userChoice;
do {
Notification procedureNotification = service.performSomeProcedure(someInputs);
userChoice = notifier.notify(procedureNotification);
} while (userChoice == UserChoice.RETRY);
The first option does not appeal to me at all
I like the second option more. Besides, I could use the same abstraction to request user confirmations ("Are you sure you want to delete this?")
Though, I'm not 100% sure about it too. Why? Because the return value won't be used 90% of the time. And if I split it into two methods, you may start to notice that it does two things: notifying the user and prompting them for feedback
public interface Notifier {
void notify(Notification notification);
UserChoice requestFeedback(Notification notification);
}
On the other hand, you can reframe the abstraction somewhat and make it responsible for interacting with the user generally. Then, it seems alright that it has one method for one-way interaction and another one for two-way interactions. I would have to come up with some generic name, and the purpose of that type would become more blurred (which is a downside)
public interface CommunicationChannel {
void notify(Notification notification);
UserChoice requestFeedback(Notification notification);
}
So what do you think? What is the right way to abstract that logic away?