oopcomputer-scienceformal-languages

OOP design of tool for teaching formal languages & automata


I am thinking of using some spare time to play around with designing and implementing a teaching tool for a course on formal languages and automata theory. I am trying to decide whether an OOP implementation would be appropriate and, if so, whether anyone can suggest high-level improvements to the design I outline below.

There are lots of potential classes revealed in the linguistic analysis. Some (and please let me know if I've missed anything fundamental) are: grammar; nonterminal; terminal; production; regular grammar; context-free grammar; context-sensitive grammar; unrestricted grammar; automaton; state; symbol; transition; DFA; NFA; NFA-Lambda; DPDA; PDA; LBA; Turing machine.

Question 1: Should each kind of grammar get its own class in the implementation, or should an over-arching grammar class have methods to determine what kind of grammar it is (e.g., "isRegular()", "isContextFree()", etc.) (more generally, should classes which differ only a little in the domain model, and only in terms of behavior, be represented via inheritance in the implementation, or is it better to simply push different kinds of behavior into the parent class?)

Question 2: Should things like "Symbol", "State", "Nonterminal", etc. get their own classes in the implementation, or should these be dominated by their containers? (more generally, should very simple classes in the domain model be given their own classes in the implementation - e.g. for extensibility - or should that be pushed into the container class?)

Question 3: Should Transition be its own class in the implementation and, if so, will I need to subclass it in order to support each kind of automaton (since, besides differing in terms of the state, they also differ in terms of what happens during transitions)? (more generally, is it good practice to have two abstract parent classes where there is a bijection between the children of one and the children of another... coupling?)

I realize that, at the end of the day, a lot of these decisions are simply design decisions, but I would like to know what you guys think about best practices in OOP design. Moreover, and the reason I'm not just asking the "more generally" questions as pure OOP design questions is that I'd like special perspective from people who have experience with this kind of domain (languages & automata).

Any help is greatly appreciated.


Solution

  • more generally, should classes which differ only a little in the domain model, and only in terms of behavior, be represented via inheritance in the implementation, ...

    Behavior is not "only". Behavior is most important part of objects.

    or is it better to simply push different kinds of behavior into the parent class?

    Certainly not. That would be violation of Liskov substitution principle.
    Inheritance should not be used for "easier access to common stuff".

    Content of parent class muss be completely ubiquitous with child classes, avoid inheritance and use composition if child does not comply w/ it.

    more generally, should very simple classes in the domain model be given their own classes in the implementation - e.g. for extensibility - or should that be pushed into the container class?

    It kind a depends on how "deep" Your logic is going to go.

    Usually it's a good idea to start decomposition only when you hit some limits.
    Some people call it evolutionary design.

    E.g. - I have "class must not be larger than ~200 lines of code", "object must not talk with more than 5-7 other objects", "method should not be larger than ~10 lines of code", "same logic should be written once only" etc...

    Also - it's easy to underestimate semantics. if order.isOverdue() is much more easily readable and understandable than if order.dueDate<date.Now(). Introducing classes that reflect concepts of domain greatly "humanizes" code - increases abstraction level (think "asm vs java").

    But decomposition for the sake of decomposition leads to unnecessary complexity.
    It always must be justified.

    Should Transition be its own class in the implementation and, if so, will I need to subclass it in order to support each kind of automaton (since, besides differing in terms of the state, they also differ in terms of what happens during transitions)?

    There is nothing wrong with that as long as it complies with Your domain.

    Creation of abstractions is artistic activity that highly depends on Your domain (e.g. advises from experts at formal languages & automata might be severely over-complicated if requirement that Your code is supposed to be as a teaching tool for a course is forgotten).