Working on a platform that currently has a CLI, but later will have a GUI.
Java.
Basically, currently has a Platform class that orchestrates the interaction of pluggable elements. Platform is a singleton.
There is a CLI class, which has Commands that can use the Platform and its plugins.
Sometimes the Commands employ Platform components (plugins) that require further user interaction (input and output). I am thinking of making a UI interface that Platform is configured with, and the components then make calls out to this to display and obtain input. An event driven solution.
Is that a valid design?
Is there something better?
I am trying to make an MVC style approach work in my head, but the problem is it seems to imply a lot of fore-knowledge in the CLI Commands (ie, the controllers), about the Platform components. That makes them tightly coupled, and I'd like to keep the commands as general as possible. That makes them easier for extenders to work on them, and makes fewer of them for the coming GUI development.
MVC pattern is useful to build the UI (be it CLI, GUI, web page, REST API or something totally different). For integration purposes, you'll want to use other patterns, for instance Facade.
Let's say you're building an ice cream machine. You have a platform interface:
package com.plaform.icecream;
public interface IceCreamMachine {
public void measureCream(int milliLiters);
public void measureCondensedMilk(int milliLiters);
public void measureVanillaExtract(int tableSpoons);
public void measureCacaoPowder(int tableSpoons);
public void mixIngredients();
public void freezeConcoction();
public Object handOverExperimentResults();
}
Then you have your model class of MVC:
package com.cli.icecream.model;
public class Icecream {
private int amount;
private Flavour flavour;
// getters, setters, toString, etc.
}
Flavours as enum:
package com.cli.icecream.model.values;
public enum Flavour {
VANILLA, CHOCOLATE;
}
Controller:
package com.cli.icecream.controller;
public class IceCreamController {
private IceCreamFacade serviceFacade;
public IceCreamController(IceCreamFacade serviceFacade) {
this.serviceFacade = serviceFacade;
}
public void handleShowingFlavours() {
// ...
}
public IceCream handleOrderingIcecream(int balls, Flavour flavour) {
// validations, etc.
return serviceFacade.getIceCream(balls, flavour);
}
}
Now the way to hide the actual details behind the product from the MVC, you can create a Facade that will integrate the two:
package com.cli.icecream.integration;
public interface IceCreamFacade {
public IceCream getIceCream(int balls, Flavour flavour);
}
and:
package com.cli.icecream.integration;
public class IceCreamFacadeImpl {
private IceCreamMachine iceCreamMachine;
public IceCreaFacadeImpl(IceCreamMachine iceCreamMachine) {
this.iceCreamMachine = iceCreamMachine;
}
public IceCream getIceCream(int balls, Flavour flavour) {
int creamMl = ConversionUtil.calculateAmountOfCream(balls);
int condensedMilkMl = ConversionUtil.calculateAmountOfCondensedMilk(balls);
int flavourSubstanceTbsp = ConversionUtil.calculateAmountOfFlavourSubstance(balls);
iceCreamMachine.measureCream(creamMl);
iceCreamMachine.measureCondensedMilk(condensedMilkMl);
switch (flavour) {
case VANILLA:
iceCreamMachine.measureVanillaExtract(flavourSubstanceTbsp);
break;
case CHOCOLATE:
iceCreamMachine.measureCacaoPowder(flavourSubstanceTbsp);
break;
}
mixIngredients();
freezeConcoction();
Object results = iceCreamMachine.handOverExperimentResults();
return ConversionUtil.convertResultsToIceCream(results);
}
}
This way the UI (MVC) consist of classes that are blissfully unaware of how the integrated platform works and vice versa, i.e. decoupled. As an added benefit, if either component changes its behaviour, all your logic to integrate the two are in one place, instead of splattered all over either application.
Related
So I'm learning JavaFX programming and MVC. The control is also its own class and isn't integrated into the view (Which I've heard is one way to go at it). I want it to be separated from the view but because I'm trying to encapsulate everything and leave everything private with limited access to the controls/nodes, I find myself using methods to do almost anything inside of my object almost entirely when using event handlers in the control.
Example (Not an actual program, just wrote it here because I have no short examples.):
View:
public class SamplePane extends BorderPane {
private TextField tfScoreOne;
private Button btnScore, btnPenalty;
private int scoreOne;
public SamplePane() {
// Some constructor
}
public void giveScore() {
scoreOne++;
tfScoreOne.textProperty().setValue("Score: " + Integer.toString(scoreOne);
}
public void takeScore() {
scoreOne--;
tfScoreOne.textProperty().setValue("Score: " + Integer.toString(scoreOne);
}
}
public void btnScoreAddHandler(EventHandler<ActionEvent> handler) {
btnOneAdd.setOnAction(handler);
}
public void btnPenaltyAddHandler(EventHandler<ActionEvent> handler) {
btnOneAdd.setOnAction(handler);
}
Control:
public class SampleController {
public ModuleSelectionController() {
// Some contorller stuff again
samplePaneObj.btnScoreAddHandler(btnScoreHandler);
samplePaneObj.btnPenaltyAddHandler(btnScoreHandler);
}
private class btnScoreHandler implements EventHandler<ActionEvent> {
public void handle(ActionEvent arg0) {
samplePaneObj.giveScore();
}
}
private class btnPenaltyHandler implements EventHandler<ActionEvent> {
public void handle(ActionEvent arg0) {
samplePaneObj.takeScore();
}
}
}
This is mostly pseudocode so forgive me if there are any errors but do you get the point? It seems very arbitrary to just be calling methods but without passing the TextField in the example its hard to not do everything without a method doing all the work.
But is that decoupled enough for MVC? I don't really wanna break encapsulation is the main issue so I can't make the controls public and operate on them directly in the controller.
Is this all just normal? I want to make sure I'm grasping it right.
There is too much that could be said about this here. I'd advise you to have a look at a JavaFX application framework and read its documentation. I learned a lot from it. E.g., have a look here: https://github.com/sialcasa/mvvmFX
Don't make the mistake and try to derive some implementation patterns yourself from all the hello world examples out there on the internet. They all don't teach you how things should be done so that they scale well for real-world projects.
We are developing a platform, where many developers will be writing their own ETL applications that use a vendor's API that is then submitted for execution onto the platform. We want to constrain developers from just dong their own thing when writing a Main class (that would normally just use the vendor's API), in order to promote some strongly-held conventions. The (large) organisation has a culture of people doing their own thing which over years has resulted in some pretty nasty architecture, so we'd like to impose some best practice conventions that can be enforced by way of CI/CD which will help foster code sharing. The alternative will be a reversion-to-the-norm free for all, which we are desperate to avoid.
How do we determine what the main class of an application is? How can we test for this? We'd like to define either an abstract class or interface that the developers make use of, which will define some up-front promises that the developers must abide by (or else the tests will fail). We can't modify the vendor's code.
So, more concretely, currently we have:
public class MyNastyFreeForAll {
public static void main(String[] ) {
//...
}
}
Is there a way of detecting/enforcing something like:
public class MyConventionEnforcingClass implements/extends MyConventions {
public static void main(String[] ) {
//...
}
}
i.e. test that the main class for the application uses something derived from MyConventions?
Ideally, I want to run the test using Spock.
Alternatively, is there a better way to achieve this aim? Code reviews in an organisation this size amongst lots of separate teams with no central control/hierarchy just isn't going cut it, I'm afraid.
EDIT TO REFLECT INPUT FROM COMMENTS:
At its heart, this is a people problem. However, the people number in their 1000s and cultural change will not happen overnight. It will not happen simply by educating, documenting and influencing and thereby hoping that people will do the right thing. I am looking for a technical solution that can gently steer our developers into doing the right thing - they can always subvert this if they wish, but I want to require them to go out of their way to do so if they want to do this. It is because I am seeking a technical solution that I am posting on SO, not seeking guidance on how to drive cultural change on another site.
EDIT TO PROVIDE MCVE:
Here's an example using an abstract class. It would be nice to validate (at compile or test time) that the main class derives from MyConventions. If a user wants to actively subvert this, then so be it - you can lead a horse to water and all that - but I'm trying to make it easier for end-users to do the right thing than to not do the right thing. Simply giving them a class that does the boilerplate for them is not likely to suffice, as these users like to do their own thing and would likely ignore you, so there should be some form of light-touch technical enforcement. There is no attempt to impose convention on doProcessing() but the same principles could be used to add pre- and post- methods etc to achieve this.
If there is another way of achieving this aim then I'd be very interested in ideas.
// MyNastyFreeForAll.java
// written by end-user
public class MyNastyFreeForAll {
public static void main(String[] args) {
MyNastyFreeForAll entrypoint = new MyNastyFreeForAll();
entrypoint.doBoilerplate();
entrypoint.doProcessing();
}
private void doBoilerplate() {
// lot of setup stuff here, where the user can go astray
// would like to provide this in a class, perhaps
// but we need to be able to enforce that the user uses this class
// and doesn't simply try to roll their own.
System.out.println("Doing boilerplate my own way");
}
private void doProcessing() {
// more things that the user can misuse
System.out.println("Doing doProcessing my way");
}
}
// MyConventions.java
// written by team that knows how to set things up well/correctly
public abstract class MyConventions {
public void doBoilerplate() {
System.out.println("Doing boilerplate the correct way");
}
public abstract void doProcessing();
}
// MyConventionsImpl.java
// written by end-user
public class MyConventionsImpl extends MyConventions {
public static void main(String[] args) {
MyConventions entrypoint = new MyConventionsImpl();
entrypoint.doBoilerplate();
entrypoint.doProcessing();
}
public void doProcessing() {
System.out.println("Doing doProcessing my way");
}
}
You and the other departments can compile all code with the AspectJ compiler, either manually from command line, via batch files, via IDE configured for AspectJ (e.g. Eclipse, IDEA) or via Maven. I created Maven setup for you on GitHub, just clone the project. Sorry, it does not use your MCVE classes because I saw them too late and did not want to start over.
Interface approach
Now let us assume there is an interface which all conforming applications need to implement:
package de.scrum_master.base;
public interface BasicInterface {
void doSomething(String name);
String convert(int number);
}
package de.scrum_master.app;
import de.scrum_master.base.BasicInterface;
public class ApplicationOne implements BasicInterface {
#Override
public void doSomething(String name) {
System.out.println("Doing something with " + name);
}
#Override
public String convert(int number) {
return new Integer(number).toString();
}
public static void main(String[] args) {
System.out.println("BasicInterface implementation");
ApplicationOne application = new ApplicationOne();
application.doSomething("Joe");
System.out.println("Converted number = " + application.convert(11));
}
}
Base class approach
Or alternatively, there is a base class applications have to extend:
package de.scrum_master.base;
public abstract class ApplicationBase {
public abstract void doSomething(String name);
public String convert(int number) {
return ((Integer) number).toString();
}
}
package de.scrum_master.app;
import de.scrum_master.base.ApplicationBase;
public class ApplicationTwo extends ApplicationBase {
#Override
public void doSomething(String name) {
System.out.println("Doing something with " + name);
}
public static void main(String[] args) {
System.out.println("ApplicationBase subclass");
ApplicationTwo application = new ApplicationTwo();
application.doSomething("Joe");
System.out.println("Converted number = " + application.convert(11));
}
}
Unwanted application
And now we have an application which does its own thing, neither implementing the interface nor extending the base class:
package de.scrum_master.app;
public class UnwantedApplication {
public void sayHello(String name) {
System.out.println("Hello " + name);
}
public String transform(int number) {
return new Integer(number).toString();
}
public static void main(String[] args) {
System.out.println("Unwanted application");
UnwantedApplication application = new UnwantedApplication();
application.sayHello("Joe");
System.out.println("Transformed number = " + application.transform(11));
}
}
Contract enforcer aspect
Now let us just write an AspectJ aspect which yields a compiler error via declare error (a warning would also be possible via declare warning, but that would not enforce anything, only report the problem).
package de.scrum_master.aspect;
import de.scrum_master.base.BasicInterface;
import de.scrum_master.base.ApplicationBase;
public aspect ApplicationContractEnforcer {
declare error :
within(de.scrum_master..*) &&
execution(public static void main(String[])) &&
!within(BasicInterface+) &&
!within(ApplicationBase+)
: "Applications with main methods have to implement BasicInterface or extend ApplicationBase";
}
The meaning of this code is: Look for all classes with main methods inside de.scrum_master or any subpackage, but not implementing BasicInterface and not extending ApplicationBase. In reality you would only choose one of the two latter criteria, of course. I am doing both here to give you a choice. If any such class if found, an compiler error with the specified error message is shown.
For whatever reason some people dislike the wonderfully expressive AspectJ native language (a superset of Java syntax) but prefer to write ugly annotation-style aspects, packing all their aspect pointcuts into string constants. This is the same aspect, just in another syntax. Choose any. (In the GitHub project I have deactivated the native aspect by letting is search for the non-existing package xde.scrum_master so as to avoid double compiler errors.)
package de.scrum_master.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareError;
#Aspect
public class ApplicationContractEnforcer2 {
#DeclareError(
"within(de.scrum_master..*) && " +
"execution(public static void main(String[])) && " +
"!within(de.scrum_master.base.BasicInterface+) && " +
"!within(de.scrum_master.base.ApplicationBase+)"
)
static final String errorMessage =
"Applications with main methods have to implement BasicInterface or extend ApplicationBase";
}
Compile with Maven
When running mvn clean compile (see GitHub project for POM), you will see this output (shortened by a bit):
[INFO] ------------------------------------------------------------------------
[INFO] Building AspectJ sample with declare error 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- aspectj-maven-plugin:1.10:compile (default) # aspectj-application-contract-enforcer ---
[INFO] Showing AJC message detail for messages of types: [error, warning, fail]
[ERROR] "Applications with main methods have to implement BasicInterface or extend ApplicationBase"
C:\Users\alexa\Documents\java-src\SO_AJ_EnforceMainClassImplementingInterface\src\main\java\de\scrum_master\app\UnwantedApplication.java:12
public static void main(String[] args) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
Error view in Eclipse
In Eclipse with AJDT (AspectJ Development Tools) it looks like this:
Just rename the main method in UnwantedApplication to something else like mainX and the error goes away.
I've been trying to do some "simple thing" in java that in javascript would look like:
// Main class
var model = new Model();
this.callback = function(e){/* do something */}
model.addListener("change", callback);
Well in java what I found so far is making the Main class deriving from java.util.Observer and Model from java.util.Observable; Then when the model will dispatch the event it will call the update method on the Main class. I found really ugly and not elegant at all. I can't even think of how I could work with this;
Is there any cleaner and flexible ways, maybe some libs to help me out here, because I have not found any acceptable tutorial about how to do it like this?
thanks a lot
Well what I've managed so far, and I quite I like it a lot more than creating "empty" classes just for simple events (but still not good, at least for me):
private ArrayList __items;
public void addListener(Method method, Object object){
this.__listeners.add(new Object[] {method, object});
}
public void dispatch(){
int i = this.__listeners.size();
Method method;
Object context;
while(i>0){
i--;
method = (Method)(this.__listeners.get(i))[0];
context = (Object)(this.__listeners.get(i))[1];
try{
method.invoke(context);
}catch(java.lang.reflect.InvocationTargetException e){
}catch(java.lang.IllegalAccessException e){
}
}
}
Then I use like this:
Gifts gifts = prendastotty.PrendasTotty.getMain().getLoggedUserGifts();
Class[] parameterTypes = new Class[0];
try{
Method m = Home.class.getMethod("__updateTable", parameterTypes);
gifts.addListener(m, this);
}catch(NoSuchMethodException e){
}
It this leaky/anti-pattern/buggy?
I must say that I had a bit of trouble keeping up with your code because in my head some of the stuff didn't make sense (from a Java way of thinking, or at least my Java way of thinking). So I hope I understood you correctly and can help you out.
Let's first take your simple example:
var model = new Model();
this.callback = function(e){/* do something */}
model.addListener("change", callback);
In Java a good approach,for example, would be:
public interface ModelListener {
public void execute(Model context);
}
public class Model {
private List<ModelListener> listeners;
public Model() {
this.listeners = new ArrayList<ModelListener>();
}
public void addListener(ModelListener listener) {
this.listeners.add(listener);
}
public void dispatch() {
for (ModelListener listener: listeners) {
listener.execute(this);
}
}
}
With this sort of design you can now do one of two things:
Use anonymous classes
In Java the most common case is that all your classes have a name, although there are cases when you can create anonymous classes, these are basically classes that
are implemented inline. Since they are implemented inline, they're usually only
used when they're small and it's known they won't be re-usable.
Example:
Model model = new Model();
model.add(new ModelListener() {
public void execute(Model model) { /* do something here */ }
});
Notice how the new ModelListener object is created (which is an interface) and the execute implementation is provided inline. That is the anonymous class.
Interface Implementations
You can create classes that implement your interface and use them instead of anonymous classes. This approach is often use when you want your listeners to be re-usable, have names that give semantic meaning to the code and/or they're logic isn't just a few lines of code.
Example:
public class LogListener implements ModelListener {
public void execute(Model model) {
// Do my logging here
}
}
Model model = new Model();
model.addListener(new LogListener());
Side note
As a side note, I saw that the method you were trying to bind as a listener was called __updateTable are you by any chance trying to detect object's changes so you can commit them to the database? If so I strongly suggest you to look at some ORM frameworks such as Hibernate or JPA they'll keep all that hassle from you, keeping track of changes and committing them to the database.
Hope it helps, regards from a fellow portuguese StackOverflow user ;)
You will find it a bit difficult to try to directly map javascript ideology into java. Their underlying philosophies are different. Without more definite code and expectations it is difficult to give you a clearer answer. Here is a sample of code in GWT(written in java) that attaches a click handler to a button.
Hope this helps you get started.
myButton.addSelectionListener(new SelectionListener<ComponentEvent>(){
#Override
public void componentSelected(ComponentEvent ce) {
// do your processing here
}
});
In Java, a function can't exist outside of a class as it can in Javascript. So when you need to provide a function implementation at runtime, you have to wrap that function inside a class and pass an instance of the class, unfortunately.
The solution you have using reflection will work (I assume), but it is not the preferred way to do it in Java since what used to be compile-time errors will now be runtime errors.
So I have this nice spiffy MVC-architected application in Java Swing, and now I want to add a progress bar, and I'm confused about Good Design Methods to incorporate a JProgressBar into my view. Should I:
add a DefaultBoundedRangeModel to my controller's state, and export it?
class Model {
final private DefaultBoundedRangeModel progress
= new DefaultBoundedRangeModel();
public void getProgressModel() { return progress; }
public void setProgressCount(int i) { progress.setValue(i); }
}
class Controller {
Model model;
int progressCount;
void doSomething()
{
model.setProgressCount(++progressCount);
}
}
class View {
void setup(Model m)
{
JProgressBar progressBar = /* get or create progress bar */ ;
progressBar.setModel(m.getProgressModel());
}
}
/* dilemma: Model allows progress to be exported so technically
all of the progress state could be set by someone else; should it be put
into a read-only wrapper? */
use JGoodies Binding to try to connect the JProgressBar's visual state to my model's state?
class Model {
private int progress;
public void getProgressCount() { return progress; }
public void setProgressCount(int i) { progress = i; }
}
class View {
void setup(Model m)
{
ProgressBar progressBar = /* get or create progress bar */ ;
CallSomeMagicMethodToConnect(m, "progressCount", progressBar, "value");
// is there something that works like the above?
// how do I get it to automatically update???
}
}
or something else???
edit: more specifically: could someone point me to a Good Example of realistic source for an application in Java that has a status bar that includes a progress bar, and has a decent MVC implementation of it?
No (to 1) and NOOOO (to 2). At least in my opinion.
No (to 1): First, DefaultBoundedRangeModel is a javax.swing class. In my opinion, these classes have no place in models. For example, think about the model living on the server, being accessed via RMI - All of the sudden putting a javax.swing class there seems "not right".
However, the real problem is that you're giving a part of your model (the bounded model) to someone else, with no control over events fired or queries made.
No (to 2): Ugh. Binding is fun but (at least in my opinion) should be used to synchronize between UI model and UI components, not between data model and UI model. Again, think what would happen if your data model lived on a remote server, accessed by RMI.
So what? Well, this is only a suggestion, but I'd add an event listener interface and add the standard event listener subscription methods (addListner(...), removeListener(...)). I'd call these listeners from within my model when I have updates going on. Of course, I'd make sure to document the calling thread (or say it cannot be determined) in order for the client (the UI in this case) to be able to synchronize correctly (invokeLater and friends). Since the listener service will be exposed by the controller, this will allow the model to live anywhere (even allowing for listeners to be remotely invoked or pooled). Also, this would decouple the model from the UI, making it possible to build more models containing it (translators / decorators / depending models).
Hope this helps.
I would say, something else.
The problem I have had with MVC, is to define the level of abstraction of the model.
Model could be some sort of objects for the UI components
Model could also be some other sort of objects for the program it self.
and
Model could be as high as business models.
In this case I would have separated model/component pairs for the progress bar and handle them in a separate controller class.
This article describes swing architecture and might clarify the way it uses models inside.
In our app (MVC, about 100 KLOC) we have it like that (pattern Observer, actually):
/**
* Observer on progress changes
*/
public interface IProgressListener {
public void setProgress(ProgressEvent e);
}
public class ProgressEvent extends ... {
private int progressCount;
// setter + getter
...
}
class Model {
public void addProgressListener(IProgressListener l);
protected void fireProgressChange(ProgressEvent e); // call .setProgress() on listeners
}
class Controller {
private Model model;
}
class View extends ProgressBar implements IProgressListener {
...
// IProgressListener implementation
public void setProgress(ProgressEvent e) {
this.setValue(e.getProgress());
}
...
}
While simple, interface-driven event notification frameworks in Java have been around since pre-Cambrian times (e.g. java.beans.PropertyChangeSupport), it is becoming increasingly popular for frameworks to use annotation-driven event notification instead.
For an example, see JBossCache 2.2. The listener class has its listener methods annotated, rather than conforming to a rigid interface. This is rather easier to program to, and easier to read, since you don't have to write empty implementations of listener callbacks that you're not interested in (and yes, I know about listener adapter superclasses).
Here's a sample from the JBossCache docs:
#CacheListener
public class MyListener {
#CacheStarted
#CacheStopped
public void cacheStartStopEvent(Event e) {
switch (e.getType()) {
case Event.Type.CACHE_STARTED:
System.out.println("Cache has started");
break;
case Event.Type.CACHE_STOPPED:
System.out.println("Cache has stopped");
break;
}
}
#NodeCreated
#NodeRemoved
#NodeVisited
#NodeModified
#NodeMoved
public void logNodeEvent(NodeEvent ne) {
log("An event on node " + ne.getFqn() + " has occured");
}
}
The problem with this, is that it's very much more of an involved process writing the framework to support this sort of thing, due to the annotation-reflection nature of it.
So, before I charge off down the road of writing a generic framework, I was hoping someone had done it already. Has anyone come across such a thing?
You can already do this today with EventBus.
Following example is from EventBus Getting Started guide. Statusbar that updates based on published events, and no need to register statusbar control/widget as listener of publisher(s). Without EventBus, statusbar will need to be added as listener to many classes. Statusbar can also be created and destroyed at any time.
public StatusBar extends JLabel {
public StatusBar() {
AnnotationProcessor.process(this);
}
#EventSubscriber(eventClass=StatusEvent.class)
public void updateStatus(StatusEvent statusEvent) {
this.setText(statusEvent.getStatusText();
}
}
A similar project is ELF (Event Listener Framework) but it seems to be less mature.
I'm currently researching about event notification frameworks on Publish-Subscribe Event Driven Programming | Kev's Spring vs Java EE Dev and the followup articles.
I've made http://neoevents.googlecode.com to handle this kind of annotation based event handler.
#actionPerformed
private void onClick() {
//do something
}
protected void initComponents() {
JButton button = new JButton("Click me!!!");
button.addActionListener(new ActionListener(this) );
}
It looks as simple as I was expecting it to be. Annotations are available for every single listener in J2SE.
Don't mistake complicated for clever. It seems to me that this would be:
A nightmare to debug
Difficult to follow (from a maintenance perspective, or someone attempting to change something 6 months down the line)
Full of if (event instanceof NodeCreatedEvent) like code. Why this is better than subclassing an adapter I have no idea!
The main problem I see here are the method parameters, which restrict which methods can actually be used for which events, and there's no compile-time help for that.
This is what makes interfaces attractive to me for observer pattern implementations like the Java event model. Tools like eclipse can autogen method stubs so you can't get the signatures wrong. In your example, it's very easy to use the wrong parameter type and never know it until an event occurs (which might be an error case several months down the line)
One thing you might try are my annotations & processor for implementing observers and null object implementations. Suppose you have
package a.b.c;
public interface SomeListener {
void fee();
void fie();
void fo();
void fum();
}
and wanted to create a listener instance. You could write
package x.y.z;
import a.b.c.SomeListener;
import com.javadude.annotation.Bean;
import com.javadude.annotation.NullObject;
#Bean(nullObjectImplementations = {#NullObject(type = SomeListener.class) })
public class Foo extends FooGen implements SomeListener {
#Override
public void fie() {
// whatever code you need here
}
}
To create a source for these events, you can write
package a.b.c;
import com.javadude.annotation.Bean;
import com.javadude.annotation.Observer;
#Bean(observers = {#Observer(type = SomeListener.class)})
public class Source extends SourceGen {
// SourceGen will have add/remove listener and fire methods
// for each method in SomeListener
}
See http://code.google.com/p/javadude/wiki/Annotations if you're interested. Might give you some other ideas as well.
Google Guava v11 has added an EventBus component that uses this style. They also explain why they decided to use annotations rather than interfaces.
I've been thinking about a generic annotation-driven event framework as well. I like the benefits provided by static typing, but the current interface-driven event model is painful to use (ugly code). Would it be possible to use a custom annotation processor to do some compile-time checking? That might help add some of the missing "safety" that we've all grown used to.
A lot of the error checking can also be done at the time that the listeners are "registered" with the event producers. Thus, the application would fail early (when the listeners are registered), possibly even at at startup-time.
Here's an example of what the generic framework I've been toying with might look like:
public class ExampleProducer {
private EventSupport<ActionEvent> eventSupport;
public ExampleProducer() {
eventSupport = new EventSupport<ActionEvent>(this);
}
#AddListenersFor(ActionEvent.class)
public void addActionListener(Object listener)
{
eventSupport.addListener(listener);
}
#RemoveListenersFor(ActionEvent.class)
public void removeActionListener(Object listener)
{
eventSupport.removeListener(listener);
}
public void buttonClicked() {
eventSupport.fire(new ActionEvent(this,
ActionEvent.ACTION_PERFORMED, "Click"));
}
}
The producer uses EventSupport, which uses reflection to invoke the events. As mentioned before, EventSupport could preform some initial checks when the events listeners are registered.
public class ExampleListener
{
private ExampleProducer submitButton;
public ExampleListener()
{
submitButton = new ExampleProducer();
EventSupport.autoRegisterEvents(this);
}
#HandlesEventFor("submitButton")
public void handleSubmitButtonClick(ActionEvent event)
{
//...some code to handle the event here
}
}
Here, EventSupport has a static method that uses reflection to auto-register the listener with the event producer. This eliminates the need to manually register with the event source. A custom annotation processor could be used to validate that the #HandlesEventFor annotation refers to an actual field of the ExampleListener. The annotation processor could do other checks as well, such as ensuring that the event handler method signature matches up with one of the registration methods on the ExampleProducer (basically, the same check that could be performed at registration-time).
What do you think? Is this worth putting some time into fully developing?
Here's a similar project called SJES.
public class SomeController {
private Calculator c1 = new Calculator();
private Calculator c2 = new Calculator();
public SomeController() {
c1.registerReceiver(this);
c2.registerReceiver(this);
c1.add(10, 10);
c2.add(20, 20);
}
#EventReceiver(handleFor="c1")
public void onResultC1(Calculator.Event e) {
System.out.println("Calculator 1 got: " + e.result);
}
#EventReceiver(handleFor="c2")
public void onResultC2(Calculator.Event e) {
System.out.println("Calculator 2 got: " + e.result);
}
#EventReceiver
public void onResultAll(Calculator.Event e) {
System.out.println("Calculator got: " + e.result);
}
}
public class Calculator {
private EventHelper eventHelper = new EventHelper(this);
public class Event {
long result;
public Event(long result) {
this.result = result;
}
}
public class AddEvent extends Event {
public AddEvent(long result) {
super(result);
}
}
public class SubEvent extends Event {
public SubEvent(long result) {
super(result);
}
}
public void unregisterReceiver(Object o) {
eventHelper.unregisterReceiver(o);
}
public void registerReceiver(Object o) {
eventHelper.registerReceiver(o);
}
public void add(long a, long b) {
eventHelper.fireEvent(new AddEvent(a + b));
}
public void sub(long a, long b) {
eventHelper.fireEvent(new SubEvent(a - b));
}
public void pass(long a) {
eventHelper.fireEvent(new Event(a));
}
}
I think this is very easy to use.
You can also check out MBassador It is annotation driven, very light-weight and uses weak references (thus easy to integrate in environments where objects lifecycle management is done by a framework like spring or guice or somethign).
It provides an object filtering mechanism (thus you could subscribe to NodeEvent and attach some filters to restrict message handling to a set of specific types only).
You can also define your own annotations to have customized declaration of your handlers.
And it's very fast and resource efficient. Check out this benchmark showing a performance graph for different scenarios using Guava or mbassador.