Ho bisogno di progettare una gerarchia di classi simile a AST per l'introspezione del codice (come l'API Java Element utilizzata durante l'elaborazione delle annotazioni). Ma non sono sicuro di come renderlo manutenibile e facile da usare.
Il mio istinto è quello di modellare il linguaggio il più vicino possibile e sfruttare appieno il sistema dei tipi. Per fare ciò, utilizzerei l'ereditarietà per modellare l'intuitivo "è un" - rapporto tra elementi di sintassi per ottenere controlli di esaurimento in switch
-espressioni. Cioè MethodElement
s e ConstructorElement
s sono ExecutableElement
s, TypeAliasElement
s, ClassElement
s e InterfaceElement
s sono TypeElement
s e così via. Ciò porta a una gerarchia di classi piuttosto profonda.
Tuttavia, il comportamento condiviso non rispetta questa gerarchia. Ad esempio: PackageElement
s, ClassElement
s e InterfaceElement
s possono racchiudere altri elementi. Ma PackageElement
s racchiude sempre solo PackageElement
s o TypeElement
s e solo ClassElement
s racchiude mai ConstructorElement
s. Le interfacce Mixin in combinazione con i delegati per l'implementazione possono essere utilizzate per condividere il comportamento tra le classi. Tuttavia, diventa complesso molto rapidamente con gerarchie di classi profonde e molte interfacce di mixaggio.
Dovrebbe esserci una singola interfaccia per elementi che sono in grado di racchiudere altri elementi? O un'interfaccia per essere in grado di racchiudere pacchetti, un'altra interfaccia per essere in grado di racchiudere tipi, un'altra per racchiudere funzioni ecc. Dovrebbero esserci proprietà separate per visibilità e modalità o semplicemente una che contenga tutti i modificatori applicabili?
L'approccio opposto (quello scelto dall'API Java Element) è quello di rendere la gerarchia piatta con interfacce meno specifiche: tutte le classi implementano la funzione getEnclosedElements: List<Element>
, con quegli elementi che non racchiudono nulla semplicemente restituendo una lista vuota . Allo stesso modo non ci sono tipi diversi per classi e interfacce. Sono raggruppati in TypeElement
con una proprietà enum che specifica di quale tipo sia realmente. Ovviamente questo rende tutto molto più semplice per me, ma non è ovvio all'utente dall'interfaccia solo quali classi possono racchiudere quali tipi di altri elementi, quindi gli utenti devono leggere la documentazione API molto più spesso e il loro codice è meno sicuro.
Quale approccio è preferibile qui? Temo che le gerarchie di classi profonde renderanno l'API non rintracciabile, difficile da mantenere (perché sono necessari così tanti mixin per condividere il comportamento tra classi simili) e fastidiosi da usare. Ad esempio: l'utente potrebbe voler scrivere una funzione per stampare tutti i membri di ClassElement
o InterfacElement
. Non possono utilizzare l'interfaccia EnclosesElements
perché un PackageElement
racchiude anche gli elementi e inoltre non può utilizzare la classe base TypeElement
poiché include TypeAliasElement
che non include mai membri. Non esiste un'interfaccia o una classe base che includa solo InterfaceElement
e ClassElement
.