Modularity

Cytoscape should be a collection of modules.

The dependencies between modules should create a DAG. A dependency means that module A needs access to a class, method, or variable in module B to compile and run. Circular dependencies (e.g. A depends on B, B depends on C, and C depends on A) should be avoided at all costs! If they can't be avoided, then perhaps they shouldn't be separate modules.

The Cytoscape "application" should sit near the top of the DAG because its role is to combine the functionality of the modules it depends on into a coherent application.

A "module" consists of one or more java packages defining interfaces and classes. The module should advertise certain packages as public, meaning other modules can use the classes and interfaces available in these public packages, and other packages as private. This defines a clear interface to the module. The public packages of a module should consist primarily of interfaces and possibly abstract implementations of interfaces. Public packages should NOT contain full implementations of interfaces. Instead, a package can provide a factory class that returns instances of interfaces. The goal is to completely hide the implementation of an interface from end users. In the eventthat concrete utility classes are made part of the public interface, these classes should ONLY operate on the interfaces and should not rely on the hidden implementations.

A module should maintain conceptual integrity. This means:

Multiple modules can form a conceptual layer. A layer should not need to be a single module or jar.

OSGi and Services

OSGi can be used to enforce this modularity because OSGi allows modules (or bundles in their parlance) to declare some packages public and some private. In addition, OSGi provides a powerful mechanism for avoiding the multitude of Factory classes that would otherwise be necessary with it's Service mechanism. This service mechanism allows for further modularization by allowing bundles to exist with NO public interface and that only register services with the service registry.

A service is not complicated, since it is simply declared as an interface. Classes that implement this service interface can then be registered with the OSGi service registry. This registry effectively takes the place of a factory class because now instead of requesting an instance of an interface from a factory, you request the service (that is an instance of an interface) from the service registry. This is analogous to asking CyLayouts for a Layout object.

Or you could request all implementations of a service. This is analogous to asking the ImportHandler to return all CyFileFilters to be used for creating a network import dialog.

Communication (Event Handling)

The general strategy for communication within the module DAG is to for modules to call methods on dependencies (modules "down" or "lower" in the dependency graph) and modules should signal that state has changed by firing events "up" the graph. Modules lower in the hierarchy should never call methods "up" in the hierarchy as this would create circular dependencies.

Here is an example. Imagine the DAG where A, B, C, and D are modules and:

B should have normal get/set state methods. C can call any public get/set method on B. However, if the state of B changes because C has called a set state method, D will need to know that B has changed. Instead of B calling a method of D or C calling a method on D notifying it of the change (thus introducing new dependencies), B should fire a state changed event, which both C and D should be listening for. Both C and D should update their internal states that are dependent on B when they "hear" the event. If the change to B somehow impacts A as well, first step back and consider whether C should be modifying A instead of B. In the case that modifying B is correct, then B should modify A through A's get/set methods. A should NOT listen to events from B because that would introduce a circular dependency.

Maintainability

The key to maintainable software is the ability to know that a change to one part of the software doesn't adversely impact other parts of the software. The usual way of accomplishing this is by defining loosely coupled modules and by writing unit tests that exercise a large percentage of the code contained in the module. The primary obstacle to good unit tests is dependencies on other classes. Dependency injection (independent of framework) is useful in addressing this problem.

A simple heuristic for evaluating a class design is by evaluating how easy the class is to write unit tests for. If a class is difficult to test because is relies on several other classes that are in turn hard to aquire, then there might be a problem.

The general approach to handling GUI testing is to isolate the actual GUI code to extent possible using the MVC design pattern and simply not test the View but focus on testing the Model and Controller.

Outdated_Cytoscape_3.0/DesignGuidelines (last edited 2011-02-24 15:34:46 by PietMolenaar)

MoinMoin Appliance - Powered by TurnKey Linux