I will try and explain this without resorting to UML or diagrams too much.
From the top of the design down. (Backwards approach I think but easier to elaborate on)
You have a novel object. This object contains Novel specific data - title / author / date published / genre / ISBN / etc.
This object also is composed of a set of smaller subclasses
The subclasses are for example ..
You also have a novelObject class which would likely be a very simple class containing a few universal pieces of data. - These are then extended to account for specific functionality.
These are then stored into the collection objects which could either implement a collection interface ala Java or extend a more general collection class.
Some specifc collections might need other pieces of data - the chapter collection would need to know when each chapter started and where each chapter stopped as well as the next and previous chapters.
By extending a collection class or implementing a well written interface the logic for the next / prev / first / last / jump to logic could be internalized and leave you more freedom to worry about the novelObjects data and structures.
$prologueCollection implements Collection / Iterator and collects $prologueObject(s) -> which extends novelObject
$chapterCollection implements Collection / Iterator and collects $chapterObject(s) -> which extends novelObject
$epilogueCollection implements Collection / Iterator and collects $epilogueObject(s) -> which extends novelObject
I also imagine some sort of name->value paired collection or collection of small objects to handle the Table Of Contents.
The Collection interface and the Iterator determine the order of each type of section. The collection for an object at the top of the stack would be assumed to be the first object to appear of it's type in the Novel output.
On the way to rendering your output you could pass each type of object through it's own specific Decorator helper class. This allows the main class to handle the data and structures and the decorator to deal with things like linkifying text headers and inserting the page numbers into the output. During this process the decorator could access a member variable(s) for each chapterObject processed thus storing it's starting and stopping page numbers ( This assumes your decorator takes the page and determines if it needs to be split into smaller portions for display ) as well as displaying the current page number for the chunk of data being rendered. Not all of the novelObjects would need to store the page number data of course and also not all of the Page Objects need to be passed through a decorator. This would easily allow you to begin page numbering at the beginning of chapter one and not number the prologue. Or to get fancy you could number prologue / epilogue novelObjects through a different decorator that uses roman numerals or alphabetic page ordering. (just ideas here)
Now the hierarchy is such that your novel contains a group of collection objects which have collected the various novelObjects they contain. All the main novel class has to do is determine the rendering order for sections and iterate through the collections as it sees fit passing certain data through specific decorators and displaying the output. This seems particularly vague as an implementation but think of the benefits.
You are leaving the page chunking for chapters to the decorator. This means if you decide to change the 'size' of a page from 25 lines to 10 lines that the page numbering and table of contents generation is handled by the decorator at runtime. A chapter that was once 2 pages long now is 6 pages long. The collection for the chapterObjects is told where the chapter starts and stops by the decorator and this information is accessible by the TOC class and nobody has to worry about it.
This also allows you to keep data specific to all novelObject types in their own super class while allowing certain extended novelObjects to keep their own private data. It also allows each collectionObject to determine how it orders / stores / manages it's members.
Also if you decide for one Novel that you need footnotes you can write a footNotes class that extended your novelObject class. This would be a million zillion times easier to work with than trying to make a huge super class for all types of novel data. If you wanted to you could also pass in a reference to specific render classes to handle the output of each collection as it gets handed to the decorator for that object type.
Being an XML man myself I would store the Novel data as an XML doc and run the xml through a parser yanking out the data required to instantiate each extended novelObject subclass and then hand each new object off to its respective collection. At the end pass the collections into the Novel object and allow it to handle the data as it sees fit for rendering. (? XML / XSLT / CSS ?)
I know this is a very complicated solution to what you were hoping was a simple problem but it is in my opinion the most extensible and forward thinking approach to a complex composited data type.
When I write code I try and think of the smallest data chunk I can work with that isn't subjective at all. A page is subjective (depends on the number of lines, width of display, font etc. ) while a Chapter or Epilogue section is not. It is simply a store for the relevent data.
Then, if you have any situation where you might have an indeterminate number of these chunks you should hand them off to a collection object that implements an interface most suited to its data. This might be an array / hash table / iterator / linked list / etc depening on what it is.
If there is a semantic separation between the raw data and it's presentation then a decorator is perfect. For example the href and target attributes for an html link are raw data. The brackets, quotes, css classing, etc are a decorated layer over that raw information.
As a final step the last class I write is the contol class (In this case the Novel class) which simply accepts the data and objects it gets instantiated with and deals with the logic of putting it together so it isn't just a pile of parts. In the theoretical case above the logic flow would be..
Read xml file -> hand each node to xml parser and determine it's type. Instantiate the appropriate extended novelObject subclass with the required data and then pass it through its decorator and then store it and
it's meta data in its particular collection class. The meta data would include page numbering, linkifying text, whatever you need.
When the data is loaded and collected the novel class would then look at the table of contents object and read the meta data from each collection - printing the start page and chapter title for each entry in the collection for example - then it would point each collection back to it's beginning point.
Then the Novel Class would look in its first collection for the first prologue object. It would render the output and iterate to the next prologue object. When the collection was empty the novel would then go the Chapter collection and do the same thing for each chapter - at this point you could make things complicated and check for references to footnote objects or anything else you wanted on a per chapter basis - when the Chapter collection was finished iterating the Novel Class would move to the next collection and handle that one as nescessary.
With this setup you could create a paged / indexed / referenced novel for web output without the novel knowing anything more than the mechanisms for handling the data types. The data types don't need to know about each other or even that other data chunks exist. The collections don't care about the number of objects they contain. They only care that they can access them in order and know when they are at the beginning / at the end / that data exists at a particular index.
It also would allow you to store much less pure data. Using the previous link example. As raw data containing the markup a link is far larger than the sum of its parts. Take the following as an example. As a single piece of data
Code: Select all
string = '<a href="http://www.some.domain.ext/path/info/page.html" name="name" class="class" id="id" target="target">text</a>'
= 117 chars
versus :
Code: Select all
$urlData = array('http://www.some.domain.ext/path/info/page.html', 'name', 'class', 'id', 'target');
= 98 chars
While this doesn't seem like a lot of storage saving you will see a difference. 1000 of the first type would occupy 14k - the second type would occupy 12k. If you can keep your decorator class for links under 2k in this situation you just saved yourself a small amount of bandwidth bill.
If you do this for page rendering and allow the decorator to generate the tags and quotes and styling on your data for every type of data you will display.. you just saved youself a lot of bandwidth and you make it easier to insert new information into existing data types.
For example if you find you need an attribute for your links that you forgot all you would have to do is alter the extended novelObject that handles that data to include it in it's structure then add a line or two to your decorator and voilia - all your images have alt tags across the board in your output.
This post is distracting me from my work and seems to be straying from a specific idea into a discussion of object composition and application design so I will stop here.
Let me know if I am at all helpful or if I am just being a windbag.
GRemm
** To address specific issues for you **
But how does the chapter know that it's been put into position 14 and thus it is chapter 14? Does it... ::gasp:: not need to know?
This is an implementation detail. A few options - not all for sure but a few - would be to insert the instances of the chapterObject into their collection in order. The first chapter added to the collection would be the first chapter in the rendered output for the chapters of your novel.
A second option is to pass the chapter number to the collection either as the array key value (in the case of a hash table of chapters) the index position (for a basic array or stack/queue) or to use a list type of structure for the collection which allows insertion of data into specific nodes. The different extended novelObjects may very well be suited to a variety of collection implementations. The key to any of these collections is that no matter the data structure you use to store the info / meta-info for the objects they contain they ALL implement a consistent iterator for traversing the collection.
My preference since a novel is such a linear format would be to read the chapter data into the objects in the order they would appear in the novel and implement the collection as a queue where each new chapter gets added to the bottom of the pile. Then pop them off the top of the queue (or rather iterate from the top down - pop implies removal) as each chapter object gets decorated and rendered.
Very helpful, but too complex. You suggest that you just don't interact with chapters. You interact with novels. And the parent is responsible for ordering, and all ordering dependant attributes (whether they be chapter labels or something else).
An easy analogy to use for determining which object you interact with is to equate it to real life. You pick up a Novel to read it. You don't pull a specific page or chapter off of your bookshelf. You may then open the novel and jump to a chapter once you have the novel open. The world outside the novel doesn't know the number of chapters until the novel is opened and you look at the table of contents.
With the approach above the table of contents would be a storage object of some sort that got populated with the information it needed as the decorator classes for each content chunk processed it's information - all of which happens during the construction of collection objects. (I am assuming we are storing the 'pre-decorated' data. You could also do this at render time by passing undecorated info from the collection through the decorator and out to the rendering logic. This might be a better approach even if you planned on supporting a variety of renders (handheld device pages vs the web vs pdf output for example)
When you actually start talking about converting this into... say... HTML, we would always be talking to the novels to get information about the chapters. It seems like a novel is too closely tied to its chapters to be seperated: no free-agent chapters/content-sections floating around. They're probably value objects anyhow.
If you have a subclass of the generic novelObject for a type of content that would be considered free agent and accounted for this in the control class (Novel) you could easily have content between chapters with no page numbers for example. Is this is what you mean by 'floating around'?
This post is getting too long. In summary I feel that a novel is a collection of other collections where each sub-collection represents a different type of data related to the novel. The Novel class represents this data as a book. Another class could use the same data collections and represent the information in a different format if you wanted to handle a different format. Even better - multi cultural formatting. You could have a western novel format that reads front to back left to right and another Novel class that could render and manage the collections in such a way that the same exact novel could be read back to front left to right.