Let me preface by saying that knowing some elementary music theory and music notation may be helpful in grasping the problem at hand.
I'm using an API called VexFlow to render notation. Basically I pass the parts of the editor's state to VexFlow to build the graphical representation of the score.
(EDIT: The following diagram is outdated as I began to rework the structure, please see the update at the bottom of the post for the new diagram) Here is a rough and stripped down UML diagram showing you the outline of my program:
In essence, a
Part has many
Measures which has many
Notes which has many
NoteItems (yes, this is semantically weird, as a chord is represented as a
Note with multiple
NoteItems, individual pitches or fret positions). All of the relationships are bi-directional.
There are a few problems with my design because my
Measure class contains the majority of the entire application view logic.
The class holds the data about all VexFlow objects (the graphical representation of the score). It contains the graphical Staff object and the graphical notes. (Shouldn't these be placed somewhere else in the program?)
VexFlowFactorydeals with actual creation (and some processing) of most of the VexFlow objects,
Measurestill "directs" the creation of all the objects and what order they are supposed to be created in for both the VexFlowStaff and VexFlowNotes.
I'm not looking for a specific answer as you'd need a much deeper understanding of my code. Just a general direction to go in.
Here's a thought I had, create an
PartView classes that contains the basic VexFlow objects for each class in addition to any extraneous logic for it's creation? but where would these views be contained? Do I create a
ScoreView that is a parallel graphical representation of everything? So that
ScoreView.render() would cascade down
PartView and call
render for each
PartView and casade down into each
MeasureView, etc. Again, I just have no idea what direction to go in. The more I think about it, the more ways to go seem to pop into my head.
I tried to be as concise and simplistic as possible while still getting my problem across. Please feel free to ask me any questions if anything is unclear. It's quite a struggle trying to dumb down a complicated problem to its core parts.
So I went ahead and started refactoring with my idea from above. Here is an updated Diagram
@JW01 I understand why bi-directional relationships are potentially bad (difficult to maintain, increased complexity), however, I do believe the increased flexibility is worthwhile and I have implemented it in a maintainable way. Each notation "container" (
Note - all contain children of some sort) inherits from
Container (this relationship isn't shown in the diagram because the program I use is shitty and it looks super messy). The
Container.addItem(item) method mixes in the
Traversable class methods and
parent property to the item added.
To me this seemed pretty clever and easy to understand. Basically, any item that gets put in a container gets extended functionality. As a mentioned in a previous comment, I may want to pull out a nice music data API out of this, which would allow for "plug-ins" to be easily created. Traversing in both directions would be particularly helpful for functionality like that. Plus these classes are small and maintainable, and unlikely to change.
However, I understand that the less things that depend on these relationships the better. So it's definitely best if I isolate this traversing functionality for use with the
I'm noticing that my
View classes are doing a lot now. Building, rendering, formatting and handling click. It would probably be wise to factor out a an abstract View module composed of a ViewBuilder, ViewFormatter, ViewRenderer, ClickBehavior?