6

I wrote a parser for a certain type of binary file with recursive structure. I made its API to be similar to SAX, that is:

  • the parser accepts an object of a specific interface,
  • this interface has several methods called as the parsing happens: startFoo(type, name), endFoo(), datum(type, name, value), badEntry(errorMsg), etc.
  • there are certain promises regarding how these callbacks are called: e.g. for each startFoo there will be an endFoo, with appropriate nesting, etc.

I used to think this is a variant of a Visitor pattern. However, the visitor pattern doesn't have multiple callbacks, these callbacks have different arguments, and the Visitor pattern doesn't talk about promises like in the last point. Also, strictly speaking, it's not an in-memory data structure being iterated over, but I guess this part is less important…

I also think it's not strictly an Observer pattern: there is no state to observe; no late registration for receiving events (ie. either you get all parsing events or none, you can't start in the middle); only a single object is accepted.

Is there a more proper name for this design pattern?

EDIT: As I understand it, design patterns exist to quickly and precisely communicate common code structures. However, if all I can say about the above code structure is that it is a "Strategy" pattern, or an "Observer" pattern, the communication is inefficient. Not all "Strategy" pattern implementations have interfaces with multiple methods with promises regarding the order in which they are called, etc.

I am looking for a name or a phrase that would directly communicate the set of conditions mentioned above, or at least some close approximation of them.

2 Answers 2

4

This is an example of the Strategy Pattern. The library supplies a generic parser that calls back into the user-supplied parsing strategy, which allows the parser to avoid keeping unneeded state. The additional guarantees regarding the sequence of method invocations are just a part of the interface that cannot be expressed within the type system as a method signature.

The intention of the state pattern is different: To allow an object to change its behaviour when its state changes, without excessive conditionals. To that end, each state is represented by a separate subclass. A wrapper contains the current state object and may change the state. Here, the parser API does not have the ability to change the callbacks. However, the supplies strategy may use the state pattern internally.

The intention of the visitor pattern is different: To add new operations to a class hierarchy without changing those classes. The defining feature is a double-dispatch approach that amounts to a safe downcast. Here, there seems to be no class hierarchy that would require a visitor. However, the parser or the strategy may use a visitor internally to deal with different elements (e.g. to react differently to text-, element-, and comment-nodes).

This is somewhat related to the observer pattern, as the parsing strategy is notified whenever the parser state changes. As there seems to be no support for registering multiple observers, so I think the characterization as an example of the strategy pattern is more accurate. However, this callback-based parsing you are seeing is absolutely a kind of event-driven parsing.

1
  • "Strategy pattern" seems overly generic to what I have in mind. It pretty much only covers the first bullet point from my list. BTW, my intention for using this pattern was exactly to make it possible to add new operations to the parser: either parse the structure for its exact contents, or just to collect basic statistics (e.g. how many elements there are)—which is what you write about the visitor pattern.
    – liori
    Jan 21, 2017 at 19:21
1

Any parser implements somehow a big state machine where the states depend on the parsed langage grammar and the lexical token read, and where the input trigger state changes.

This doesn't mean that the parser uses a state design pattern. You could implement parsers using builder, factory and other patterns. Or no pattern at all.

Interestingly, your receiving object also implements a state machine: it offers an interface, and you expect its functions to be called by the parser depending on its state (e.g. End() after start()). And most of the calls would trigger some state changes.

The callbacks somehow help the object to replicate the states of your parser, which forwards the state change events.

So at a macro level and based on the intent, you implement an observer pattern; Even if the updating interface of your observer is a more complex one than the traditional update().

Your Answer

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

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