Multiple Component Interactions / Observer Pattern

All:

I have an open source neural network program (http://www.simbrain.net/) which involves multiple neural networks (simulated brain circuits) talking to multiple environments. In principle the networks and environments should also be able to talk to each other.However, as the program has grown the code which manages these transactions has started to "smell," and refactoring is needed.

Currently we use a combination of the observer pattern and objects called "couplings," which contain references to environments and strings which are used to push or pull data to / from environments. Each neuron has a coupling.When a world is changed an event is fired and the neuron pushes and pulls data through all of its couplings. I have not replaced the couplings with events because this seems like it would be overkill, and that it would affect performance.There are lots of other issues as well, which I've been trying to document on our developer wiki (see links below).The basic issue is that it doesn't feel right.There are these separate mechanisms of listeners and events on the one hand, and couplings on the other, and they need to be maintained and updated and kept in sync. It's easy to get lost, is bug prone, and it does not feel well engineered.I just have this sense that there are a few patterns or design principles that software engineers know about but that, being self-taught, I'm totally overlooking. For more see:

http://www.simbrain.net/trac/wiki/Workspace

http://www.simbrain.net/trac

I am anxious to add more environments and to create more complex interactions between components, but before I do I want to have a well engineered framework in place for handling transactions between components.So, to boil this all down to a few questions:

1) Are there alternatives or supplements to the observer pattern or common ways of handling this kind of problem, where a bunch of things need to talk to each other but the observer pattern by itself is not enough?

2) Maybe I'm asking too general of a question.Every time I sit down to try to think this out or even write a post like this, a bunch of things come to mind. In that case, any suggestions on the kinds of questions I should be asking or the type of process I should follow in pursuing the refactor?

Thanks for any advice,

- Jeff

[2371 byte] By [jyoshimia] at [2007-11-27 3:24:34]
# 1

Hi Jeff,

Its very difficult to advice on complete architecture as these things are very context-sensitive.

Basically what patterns are solutions for commonly occurring problems and hence to we need to tweak them for our special problem.Just try to incorporate basics of OO programing like minimum dependency etc.

What i can suggest first right down what you feel is wrong and can cause potential problem and than think about them.This is also a good way to document design.

khangharotha at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 2

Hi!

I was looking the framework's API and what I can suggest is a few things:

- since the project is open source, publish an architecture document, this will make easier to find design errors or how to improve the architecture.

- If you don't have any UML diagrams, you can use tools to perform "reverse engineering" and obtain an UML diagram of the API. (Borland together 6.1 is my favorite for this).

- Your question about Observer pattern is quite difficult to answer from the information provide. Try to, based on the design, see if applying the pattern to the design it helps to achieve the goal or solve any problem, if you apply it and the benefits are unclear, don't use it.

My experience with the observer is very limited and you should very careful with this patter because it affects a lot the performance of the application in case of many transaction - messages.

I hope this suggestion help you. Best regards

skalstera at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 3

Khangharoth and Skalster:

Thanks for your helpful comments.It looks like the main thing I need to do is try to formulate the problem(s) I'm dealing with more clearly. I was just reading about a java engineer (I think it was Josh Bloch) saying that the most important step in software engineering is clearly defining the problem to be solved.At any rate, I will:

- Work on an architecture document (for now I'm updating the developer wiki, where I'm keeping running notes)

- Try to get UML going (I am on a Mac and have not yet found a good free method for automatically generating UML)

- Heed your suggestion to ditch a pattern if I apply it and the benefits are unclear.

As I move forward if I feel I am able to formulate more specific questions clearly, I will certainly post them here.

Best,

- Jeff

jyoshimia at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 4

> Currently we use a combination of the observer

> pattern and objects called "couplings," which contain

> references to environments and strings which are used

> to push or pull data to / from environments. Each

> neuron has a coupling.When a world is changed an

> event is fired and the neuron pushes and pulls data

> through all of its couplings. I have not replaced

> the couplings with events because this seems like it

> would be overkill, and that it would affect

> performance

Can you give a concrete example of what you are calling a 'coupling'?

dubwaia at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 5

> Thanks for your helpful comments.It looks like

> the main thing I need to do is try to formulate the

> problem(s) I'm dealing with more clearly. I was just

> reading about a java engineer (I think it was Josh

> Bloch) saying that the most important step in

> software engineering is clearly defining the problem

> to be solved.

He couldn't be more right. My experience is that 90% of problems with software come from poor requirements definition i.e. problem descriptions.

But there's no time like the present. Can you explain, in plain language what you are trying to do solve here?

dubwaia at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 6

> - Try to get UML going (I am on a Mac and have not

> yet found a good free method for automatically

> generating UML)

Don't know exactly what you mean here by "automatically generating UML".

It sounds like your writing code and then, [looking for a way to] generate UML diagrams from the code.

UML is a language for designing software. The design process is first, and then you can automatically generate code from the UML diagrams. Not the other way around. Or, you can manually generate code while reading the diagrams.

To write code first then generate UML diagrams is good, but you are not using UML to design the software if you do so. Here you are only creating artifacts/documentation and not taking full advantage of the mechanisms and software design efficiencies that UML offers.

I would suggest to try to design an application first with UML diagrams. And then, after the diagrams are done, then write/generate the code.

GhostRadioTwoa at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 7

This isn't related to your question here per se but I was poking around in your source (squeaky clean btw. very nice) and one thing that looked a little suspicious to me was that in your Neuron class, you have two collections of Synapses (fanIn, fanOut) that you have made private and you have methods to add and remove synapses but then you have a public methods that return references to these collections. This is a little like locking the windows and leaving the front door wide open. The other part of this is that I couldn't really guess what class actually traverses the graph. In strictly orthodox OO designs, you generally would have the Neurons traverse themselves so to speak.

This isn't to say that what you are doing is wrong. I don't have any experience or knowledge of this problem area so you may be way ahead of me in thinking it through. It's just that it looks odd to me and because you alluded to wanting a software engineering perspective, I thought I'd mention it.

Oh yeah, this does relate to your question in one way. When I first looked at the fanIn and fanOut collections (fanOut more) I thought that you were using the Observer pattern there. But then I couldn't find the code to propagate events in the Nueron. I also looked at a subclass of Neuron but couldn't find anything there either.

I'm very interested in this field (neural nets) but have no experience or non-trivial knowledge about it. But I do consider myself to be a strong designer and Java developer. Would you be interested in bringing someone like me into your project?

dubwaia at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 8

Check out Poseidon UML: http://www.gentleware.com/

It's Java based and should theoretically run on a Mac. I've used it in the past and it was decent.You should be able to at least get some class diagrams for your documentation.

[update] I just noticed that reverse engineering isn't included in the community edition.

dubwaia at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 9
http://www.netbeans.org/products/uml/Claims to do reverse engineering. Free.
dubwaia at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 10

These continue to be extremely helpful suggestions.Some responses:

ANOTHER PASS AT DEFINING THE PROBLEM TO BE SOLVED

In the spirit of good definitions being 90% of things, here is another pass at defining the problem, at the use-case level, based on conversation with a collaborator (live conversation seems to be an important part of the design process):

The application involves N components each of which has X receivers and Y producers, where the receivers react to numerical data and the producers produce it.The user should be able to connect any arbitrary producer with any arbitrary receiver.

"Components" are typically windows in the app's desktop.Producers and receivers are inside of these: neurons that produce and receive values, x and y coordinates in a simple gauge, the noses and ears of creatures in virtual worlds. There are more use-cases also (e.g. ability to persist the connections between producers and receivers, ability to intelligently reconnect a component that is closed and reopened, ability to "run" components separately or collectively, etc..), but I'll leave it at that.

UML

I'm pretty bad about using UML to design up-front: what I had in mind was the reverse engineering. I'll check out netbeans. Other things I don't do are write unit tests and create exception classes.Once I get in to a good practice (or out of a bad one) it becomes natural but it's just doing it those first few times...

COUPLINGS

In terms of the current design, couplings are objects that neurons have which contain direct references to "agents" in environments, e.g. a mouse in a virtual world. They basically represent the interface between a brain's sensory / motor neurons and the world the brain is in. When a network is updated (1) all the sensory neurons are iterated through and set to values based on the mouse's sensory state, and (2) the motor neurons are iterated through, and their values are used to tell the mouse what to do: go left 4 steps, wiggle your nose, etc.

NEURONS AND SYNAPSES

Thanks for your design suggestions here.This part of the code has been refactored before but it could always get better.I was also suspicious of this implementation of the graph structrue: edges have references to their vertices and vertices have references to their edges--seems redundant. It sort of emerged as I was coding: a bad sign.However, I checked with someone at IBM who had an open source graphing framework that I considered using, and he said they did it too, so I went with it.It certainly makes a lot of the coding easier.Oh, the graph is traversed at the root network level (the root network contains lists of neurons, synapses, and subnetworks). Different types of networks have to run through the vertices of the graph in different ways, in a manner that would be hard to do using local update rules only.One unique thing about Simbrain is that it allows users a lot of flexibility in the topology of their networks, but this has created a lot of design headaches too!

Regarding observer pattern in the model network itself I have a feeling firing a bunch of events in each neuron would slow things down considerably.Plus it would make the internal code of networks harder to deal with. Those were my reasons for not using observer there.Finally, I appreciate your comment about fan-in and fan-out being accessible.I'll try removing getFanIn() and getFanOut() and seeing what would have to be changed--it could improve the design.

- Jeff

jyoshimia at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 11

> > I'm very interested in this field (neural nets) but

> have no experience or non-trivial knowledge about it.

> But I do consider myself to be a strong designer and

> Java developer. Would you be interested in bringing

> someone like me into your project?

We would love to have you involved!Send me an email at your earliest convenience (jyoshimi-at-ucmerced-dot-edu).

jyoshimia at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 12

> The application involves N components each of which

> has X receivers and Y producers, where the receivers

> react to numerical data and the producers produce it.

> The user should be able to connect any arbitrary

> producer with any arbitrary receiver.

Let's refine this a little.

Do the consumers connect directly to the producers in addition to connecting each connecting to the components or do one or more components have to come between each receiver and producer?

> UML

>

> I'm pretty bad about using UML to design up-front:

> what I had in mind was the reverse engineering. I'll

> check out netbeans. Other things I don't do are

> write unit tests and create exception classes.Once

> I get in to a good practice (or out of a bad one) it

> becomes natural but it's just doing it those first

> few times...

Unless you have a tool that does round trip engineering (preferably continuously) I don't think it's worth the time to document the design in great detail. If the design gets out of sync with reality it becomes fairly useless.

> Thanks for your design suggestions here.This part

> of the code has been refactored before but it could

> always get better.I was also suspicious of this

> implementation of the graph structrue: edges have

> references to their vertices and vertices have

> references to their edges--seems redundant. It sort

> of emerged as I was coding: a bad sign.

Doing this makes traversing the graph in either direction fast and given that's what you need to do it's definitely the right choice IMO. The important thing is that they don't create different pictures of the graph. This is best accomplished by minimizing the amount of code that can add new edges and vertices. More on this below.

> Different types of networks have to

> run through the vertices of the graph in different

> ways, in a manner that would be hard to do using

> local update rules only.

I should really get a better grip on what you are doing but I'll give an example in a later post of what I am talking about.

> Regarding observer pattern in the model network

> itself I have a feeling firing a bunch of events in

> each neuron would slow things down considerably.

> Plus it would make the internal code of networks

> harder to deal with. Those were my reasons for not

> using observer there.

I'm not sure I understand why using the observer pattern would cause these problems. In particular, I'm not sure how the 'coupling' design above would require less resources. It actually seems to require more Objects and the benefit is unclear to me.

> Finally, I appreciate your

> comment about fan-in and fan-out being accessible.

> I'll try removing getFanIn() and getFanOut() and

> seeing what would have to be changed--it could

> improve the design.

A quick way to firm things up is to use the Collections.unmodifiableList() method.

public List<Synapse> getFanIn() {

return Collections.unmodifiableList(fanIn);

}

if you are worried about performance...

List<Synapse> readOnlyFanIn = Collections.unmodifiableList(fanIn);

public List<Synapse> getFanIn() {

return readOnlyFanIn;

}

The reason I would do this has to do with the comment I made above about keeping the in and out graphs consistent. This will make it a lot easier to guarantee.

dubwaia at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 13

Here's an example of having the Neuron manage traversal. It's very simple and naive (e.g. doesn't detect cycles) but should give you an idea about what I am talking about.

interface Traverser {

public void receive(Synapse s);

public void receive(Neuron n);

}

public void traverseIn(Traverser t) {

t.receive(this);

for(Synapse s : fanIn) {

t.receive(s);

s.getSource().traverseIn(t); // is this the right one?

}

}

I noticed that Synapse is implemented like this:

public void init() {

target.getFanIn().add(this);

source.getFanOut().add(this);

setDelay(0);

}

I would do this instead:

public void init() {

target.addFanIn(this);

source.addFanOut(this);

setDelay(0);

}

The real problem here is that you are making the internal structure on Neuron part of the public API. At the very least you should remove ArrayList from the public declarations and use List instead.

dubwaia at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 14

> Do the consumers connect directly to the producers in

> addition to connecting each connecting to the

> components or do one or more components have to come

> between each receiver and producer?

The consumers connect directly to the producers; no components have to come between receivers and producers. It may be that some type of abstraction in the middle would help though, e.g. an ability to set rules for delaying the message from producer to consumer.

> I'm not sure I understand why using the observer

> pattern would cause these problems. In particular,

> I'm not sure how the 'coupling' design above would

> require less resources. It actually seems to require

> more Objects and the benefit is unclear to me.

>

I should clarify that couplings are for relations between components, e.g. networks and environments, not for relations inside one network between neurons (having said that, your are right that couplings are a problem!).More on inter-neuron communication in response to your other post.

> > Finally, I appreciate your

> > comment about fan-in and fan-out being accessible.

> > I'll try removing getFanIn() and getFanOut() and

> > seeing what would have to be changed--it could

> > improve the design.

>

> A quick way to firm things up is to use the

> Collections.unmodifiableList() method.

> >public List<Synapse> getFanIn() {

>return Collections.unmodifiableList(fanIn);

> }

>

> if you are worried about performance...

> > List<Synapse> readOnlyFanIn =

> Collections.unmodifiableList(fanIn);

>

>public List<Synapse> getFanIn() {

>return readOnlyFanIn;

> }

>

> The reason I would do this has to do with the comment

> I made above about keeping the in and out graphs

> consistent. This will make it a lot easier to

> guarantee.

Thanks for these suggestions. I will try them at my earliest convenience (grading finals now!).

jyoshimia at 2007-7-12 8:27:20 > top of Java-index,Other Topics,Patterns & OO Design...
# 15

> Here's an example of having the Neuron manage

> traversal. It's very simple and naive (e.g. doesn't

> detect cycles) but should give you an idea about what

> I am talking about.

>

> > interface Traverser {

> public void receive(Synapse s);

> public void receive(Neuron n);

>}

>

>public void traverseIn(Traverser t) {

>t.receive(this);

>for(Synapse s : fanIn) {

> t.receive(s);

> s.getSource().traverseIn(t); // is this the right

> one?

>}

>

That's very interesting. I have to admit I don't have my mind wrapped around it yet, but I'll keep thinking about it.

In the current design, the way the neurons are traversed is set by the user. See http://www.simbrain.net/trac/browser/trunk/src/org/simnet/interfaces/RootNetwork.java (line 211 in the current revision).For default update, we simply iterate through each neuron, and call the neuron's update function.Different neurons make use of fanIn and fanOut in different ways. Similarly with synapse update.We iterate through all the synapses and call an update function. They make use of their source and target neurons in different ways.

> I noticed that Synapse is implemented like this:

> >public void init() {

>target.getFanIn().add(this);

>source.getFanOut().add(this);

>setDelay(0);

> }

>

>

> I would do this instead:

>

> >public void init() {

>target.addFanIn(this);

>source.addFanOut(this);

>setDelay(0);

> }

>

>

I'll do that ASAP also. Thanks. Of course, I'd be happy to give you write access to the repository. You are welcome to make changes, and we are far away from our next release.

> The real problem here is that you are making the

> internal structure on Neuron part of the public API.

> At the very least you should remove ArrayList from

> the public declarations and use List instead.

Time permitting, can you say some more about this and help my java education along? I intuitively see that List is more abstract than ArrayList, so we should only present the more abstract stuff to the public interface of Neuron.Is that the general idea?

Thanks,

- Jeff

jyoshimia at 2007-7-21 20:45:59 > top of Java-index,Other Topics,Patterns & OO Design...
# 16

> I should clarify that couplings are for relations

> between components, e.g. networks and environments,

> not for relations inside one network between neurons

> (having said that, your are right that couplings are

> a problem!).More on inter-neuron communication in

> response to your other post.

My point isn't necessarily that couplings are a problem. I'm just not sure that using the observer pattern would be any more expensive. Honestly, I think you are basically using it with some variations. There may be some good stuff there.

> Thanks for these suggestions. I will try them at my

> earliest convenience (grading finals now!).

If you can I would even go so far as to make these package protected. You could then make it so that the only way to connect Neurons is with Synapses. If I'm not mistaken (and I wouldn't bet that I am not) that seems to be what you really want.

dubwaia at 2007-7-21 20:45:59 > top of Java-index,Other Topics,Patterns & OO Design...
# 17

> That's very interesting. I have to admit I don't

> have my mind wrapped around it yet, but I'll keep

> thinking about it.

>

> In the current design, the way the neurons are

> traversed is set by the user. See

> http://www.simbrain.net/trac/browser/trunk/src/org/sim

> net/interfaces/RootNetwork.java (line 211 in the

> current revision).For default update, we simply

> iterate through each neuron, and call the neuron's

> update function.Different neurons make use of

> fanIn and fanOut in different ways. Similarly with

> synapse update.We iterate through all the synapses

> and call an update function. They make use of their

> source and target neurons in different ways.

The nice thing about this approach is that it allows each Neuron to control it's traverse method. If you want to get fancy, and it sounds like you do, you can also give the Traverser some control. It's really just an organization technique, in a way.

> > I noticed that Synapse is implemented like this:

> > > >public void init() {

> >target.getFanIn().add(this);

> >source.getFanOut().add(this);

> >setDelay(0);

> > }

> >

> >

> > I would do this instead:

> >

> > > >public void init() {

> >target.addFanIn(this);

> >source.addFanOut(this);

> >setDelay(0);

> > }

> >

> >

>

> I'll do that ASAP also. Thanks. Of course, I'd be

> happy to give you write access to the repository.

> You are welcome to make changes, and we are far away

> from our next release.

I will send you an email soon.

> > The real problem here is that you are making the

> > internal structure on Neuron part of the public

> API.

> > At the very least you should remove ArrayList from

> > the public declarations and use List instead.

>

> Time permitting, can you say some more about this and

> help my java education along? I intuitively see that

> List is more abstract than ArrayList, so we should

> only present the more abstract stuff to the public

> interface of Neuron.Is that the general idea?

You might end up deciding that you want to keep providing the lists of the links publicly. However, if you decide to change the internal structure of the of the Neuron, then you will have to keep providing an ArrayList, even if you are using a custom structure. If you use List, you can write your own version of List and the users shouldn't care. Technically, you can do this with ArrayList, it's just a little messy and there are limits to what you can do. There's no particular reason to use ArrayList here so you might as well use List. More concretely, if you want to make these read-only and want to use the built in Collections wrapper, you need to return List.

dubwaia at 2007-7-21 20:45:59 > top of Java-index,Other Topics,Patterns & OO Design...