Come dice Anders , riguarda in parte le prestazioni e in parte il blocco dei progetti mal concepiti per ridurre il portata dei problemi causati in seguito da persone che ereditano ciecamente cose che non sono state progettate per essere ereditate.
Le prestazioni sembrano ovvie - mentre la maggior parte dei metodi non viene notata sull'hardware moderno, un getter virtuale potrebbe causare un notevole successo, anche su hardware moderno che si basa sempre più su istruzioni facilmente predette nella pipeline della CPU.
Ora il motivo per rendere tutto virtuale di default sembra essere guidato da una sola cosa: i framework di testing unitario. Mi sembra che questa sia una cattiva ragione, in cui stiamo modificando il codice per adattarlo al framework piuttosto che progettarlo correttamente.
Forse il problema è con i framework usati qui, dovrebbero migliorare per consentire la sostituzione delle funzioni in fase di esecuzione con shim iniettati piuttosto che costruire codice che faccia questo (con tutti gli hack per aggirare i metodi privati e quelli non virtuali , per non parlare delle funzioni statiche e di terze parti)
Quindi il motivo per cui i metodi non sono virtuali di default è come ha detto Anders, e qualsiasi desiderio di renderli virtuali per adattarsi a qualche strumento di test unitario è mettere il carrello davanti al cavallo, il design corretto supera i vincoli artificiali.
Ora è possibile mitigare tutto ciò usando le interfacce o rielaborando l'intera base di codice per utilizzare componenti (o microservizi) che comunicano tramite il trasferimento di messaggi invece delle chiamate di metodo direttamente collegate. Se si ingrandisce la superficie di un'unità per testare come componente, e tale componente è completamente autonomo, non è necessario avvitarlo con l'unità per testarlo.