1

I want to represent an Edifact message in an OOP Language. An Edifact message has the following structure:

message      :=  [ ( segment | segmentgroup ) ]
segmentgroup : = [ ( segment | segmentgroup ) ]

In words: A message is a list of segments or segmentsgroups. Segments are the base element, segmentgroups have a recursive definition ( the same as the message).

How do I represnt this in OOP? I could introduce an artfical element, say messagenode, and make both segment and segmentgourp inherit from this element. Then message woiuld be an array of messagenode. But that does't feel right for me, because when I process a message, would always do type checks, as i process them completly different. Is there a better solution?

2

How do I represnt this in OOP? I could introduce an artfical element, say messagenode, and make both segment and segmentgourp inherit from this element. Then message woiuld be an array of messagenode. But that does't feel right for me, because when I process a message, would always do type checks, as i process them completly different. Is there a better solution?

What you describe here is almost the Composite Pattern. The missing element is to make segmentgroup contain a list of messagenode-s, and to come up with a useful generalized interface on messagenode that would be expressive enough to let you avoid type-checks (and write all code against that interface).
This could work, depending on what you need to do with the data - and if the nature of the problem you are trying to solve fits this structure, then go for it.

But, sometimes that's not easy or possible. In any case, you have a tree (or a forest) here. In functional languages, as you traverse, you may rely on pattern matching, where you essentially write procedures for each specific representation (or type of node).

There is a tradeoff here involved - this approach makes it easy to add new operations for existing representations, but harder to add new representations (essentially, here, the specific data structures are part of the contract everything else relies on, even though the client code doesn't know the concrete type). But it looks like this is exactly the situation you have here (as these are part of a standard, so you are more likely to vary the processing logic). With OOP and inheritance, it's the other way around - it's hard to add new operations (or change the interface), since it's the interface that's the central abstraction, but it's easy to derive a new class. This tradeoff is known as the "expression problem" (every language, OO or not, faces it in some way).

In OOP, one way to get a structure with the first property is to use the Visitor Pattern, but that's often an overkill as the whole thing is pretty clunky. I'll say a few words for the sake of completeness, though (I'll use the terminology from the linked article). In this context, concrete visitors (VisitorOne, VisitorTwo, ...) represent different operations - so you can easily add new operations by adding another implementation for the Visitor interface. Note that this interface hardcodes the "execute" methods (here called "visit") for each element type (making it hard to add new kinds of elements). The Element hierarchy defines the different representations (say, node types in a tree structure), and also defines the "accept" method on the Element interface, so that you can accept (and call) a "visiting" operation. So, instead of working with ordinary functions, client code knows about the different visitors (operations), and selects one to "call" by passing it to the accept method on some object of the element structure (say, a root node in a tree). From then on, it all works out without having to do type checks, essentially via a clever trick: on each node, an operation is called polymorphically via the accept method, which in turn exploits function overloading rules to call the correct function for the concrete element type.
As I've said, it's quite a bit clunky, but it has it's uses.

A simpler alternative (still speaking about the case where you want more flexibility for the operations), is to treat the objects as simple data structures, and just do type checks in your functions (you wouldn't necessarily define them on the objects themselves). I know it feels wrong, but it keeps the related operations in one function, and if you keep those functions themselves together, it's not really hard to understand and maintain - as the "selection" logic is not scattered around the codebase. It's not the OOP-approach, but hey, who said that you can't mix and match designs and paradigms when it makes sense to do so?

You could try and do something more sophisticated (maybe by exploiting some aspect of the problem), and somehow encapsulate the type checks so that you don't repeat them across different functions, but there's a cost in complexity (among other things), so I wouldn't go out of my way to make it more OOP, or to avoid the if-statements. Some OOP languages, like C#, support pattern matching, and this can make the code cleaner, but on a fundamental level, it's not that different from a string of if-statements. Other things may also come into play and influence your design decisions - such as the way you want to componentize your code.

If there are some aspects of the problem that can be expressed in terms of the Composite pattern, you may even end up combining the two approaches in some way. In any case, in light of the discussion above, examine what kind of processing you need to do, what kinds of changes you are more likely to make (or what kinds of changes you'd rather support), and then base your design around that.

  • Thanks for the elaborated answer, I learned a lot! – Martin Böschen Jul 16 at 20:17
1

This is a good use case for the composite pattern. Basically, you create a MessageNode interface with a method called process() or whatever, then Segment and SegmentGroup both implement that interface. You can just call messageNode.process() and polymorphism means the process() in Segment or SegmentGroup will get called depending on which it is. You don't have to do type checks.

  • That's pretty hard to visualize without a code sample. – Robert Harvey Jul 15 at 20:07
  • 1
    What Karl describes is also in some sense the Replace Conditional with Polymorphism Refactoring. You never "need" a conditional in an OO language. (Proof: Smalltalk and its derivatives do not even have conditionals.) Although sometimes, code might be easier to read with one. (Proof: Smalltalk and its derivatives have a Boolean#ifTrue:ifFalse: library method.) – Jörg W Mittag Jul 15 at 20:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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