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 .