In primo luogo, qualsiasi classe che scrivi dovrebbe essere ereditabile / estensibile. C # sembra sigillare tutte le sue classi di sistema e quindi non mi mette fine ai problemi. L'ereditarietà è ingannevole, in particolare quando il programmatore della superclasse non sa di cosa avrà bisogno il programmatore della sottoclasse e il sottocluster non può modificare la superclasse. (E in alcuni casi non si può nemmeno guardare il codice della superclasse.) Ma l'ereditarietà può dare ai tuoi utenti finali un sacco di energia per il minimo sforzo da parte loro.
Altrimenti, andrei con la composizione. Spesso inizio con una classe base, la estendo in tutte le direzioni, quindi mi rendo conto che la classe base dovrebbe essere un'interfaccia. Se è tutto nel mio codice, questo non è un problema, ma le persone che usano la tua biblioteca rimarranno bloccate con la tua scelta. In Java ho finito con due classi che sembrano identiche per quanto riguarda le mie esigenze. Se potessi fare in modo che ognuno di loro implementasse la mia semplice interfaccia (aggiungendo "extends myInterface" - entrambi hanno già i metodi) sarei tutto pronto, ma devo trattarli come classi distinte, non correlate.
L'idea migliore potrebbe essere quella di fornire delle buone interfacce e implementazioni predefinite in modo che gli utenti finali possano fare tutto ciò che vogliono. Suppongo che il mio punto sia che gli utenti della tua biblioteca dovrebbero essere quelli che devono prendere la decisione sulla composizione e l'ereditarietà; il tuo compito è dare loro la possibilità.