0

I need to implement a Cursor class to perform operations on a Document object, which is implemented internally as a simple list of rows. The Cursor class must have methods such as moving around the Document, add lines, remove lines, insert text in a given position etc.

The problem I have is relative to the details of the design. To me, it seems that the cursor must know the implementation details of the referred class. In C++, these classes must be friend classes, but I don't like it for obvious reasons (encapsulation etc.). I can't envision an interface for the Document class to keep its implementation private that also doesn't defeat the purpose of a Cursor class, that is, I end up having the interface of the Cursor on the Document class itself.

Is leaking Document implementation details mandatory when implementing a Cursor? If not, what should the Document interface be?

1
  • I think the crux of the question is not how to design Cursor, but rather how to define the "atoms" of the document and the "location" of the cursor. Those are the pieces that need to be generic, and the cursor will follow. So for example, the Document should perhaps consist of Nodes with some notion of say an iterator (see C++ iterators for a good but mind-boggling time) to allow for insertion etc. The tricky part is defining Node. Note that here the iterator may very well be walking a tree, not a list.
    – J Trana
    Commented Apr 27, 2014 at 3:30

3 Answers 3

1
+100

Sounds like Cursor represents a position in a Document. The Document class should ultimately be responsible for all primitive operations like inserting, deleting, grabbing text and moving around. Most of these operations require a position as input and some also return a position as output. Cursor could just be a concrete data class which just holds the position and the Document, basically a pair. The other approach is to make Cursor a first class abstraction. In that case its API will just mirror that of Document, and each method will be just a thin wrapper around the corresponding method on Document, but the methods on Cursor will omit the position parameter since they will fill that with the position from the Cursor. The basic pattern is something like

cursor.someMethod(arg1, ...) = cursor.doc.someMethod(cursor.pos, arg1, ...)

except if the Document method returns a position then the Cursor method should package that together with the document and to return a cursor like

cursor.otherMethod(arg1, ...) = new Cursor(cursor.doc,
                                           cursor.doc.otherMethod(cursor.pos, arg1, ...))

The drawback of this approach is the boiler plate on the implementation side, but it makes the library much nicer to use on the client side. Since the required boilerplate is simple and systematic you may be able to avoid writing it manually by either generating it, or doing the delegation through reflective calls if you are using a language with decent reflection capacities or a reasonably powerful preprocessor.

You still need to decide what a position is. It can be just a number representing the character position in the whole document (eg Emacs), a pair representing the line and position on the line, or a in C/C++ a pointer directly into the underlying container. I would recommend making position a member type on Document if your language supports that. That way each Document implementation can supply its own position type definition. Cursor treats positions as completely abstract, it just hold them and passes them to Document methods. only Document cares how they are implemented.

0

In my opinion, it makes no sense to make Document an abstraction but have it be used through a separate class. If you intend to make the implementation of Document private, then the Cursor is part of the Document's public API - why not just get rid of the middleman and move those methods to the Document class?

2
  • Because in some cases you want to keep them separate, like for example if you need multiple cursors acting at the same time on the document. Commented Apr 26, 2014 at 18:15
  • @StefanoBorini Even in that case, the Cursor type only needs to contain the information necessary for the document to distinguish one cursor from another - it'd be a simple data container. All the relevant cursor logic can still be contained in the Document class without Cursor having to know anything about its implementation.
    – Doval
    Commented Apr 26, 2014 at 22:00
0

One solution is to make the curser pass the x and y coordinates to the document it is over, and let the document decide what row it is in. You could also have the curser call separate methods or pass in a parameter to tell the document if it is a request to remove a row, insert text, delete a row, ext.

To take this line of thought to another level, you could also create classes for your rows. The rows and document would both implement a mouse listener abstract class. This would allow the mouse to pass a click event to the document to determine the row it is in, and pass it to the row to determine the character position on the row the event was on.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.