Не используйте наследование, когда будет делать делегирование. Посмотрите на шаблон проектирования Strategy для руководства по этому вопросу.
Переход "N - G" может быть лучше обработан при наличии подкласса N (N_g), который является унарным оператором (где другие N являются двоичными) и будет делегировать работу связанному объекту G. Поддерево G тогда - фактически - непересекающееся семейство классов, основанных на G, а не на N.
T
N
/ \
N N
/ \
N_g N
|
G
/ \
G G
"Одна из проблем заключается в том, что я заранее не знаю, будет ли следующий N равен N или N_g."
"заранее?" До чего? Если вы создаете N и затем пытаетесь решить, должны ли они быть N_g, вы пропустили несколько вещей.
Вы создали экземпляр N слишком рано в процессе.
Вы забыли написать конструктор N_g, который работает путем копирования N.
Вы забыли написать replace_N_with_Ng
метод, который «клонирует» N для создания N_g, а затем заменяет исходный N в дереве на N_g.
Смысл полиморфизма в том, что вам не нужно заранее знать, что есть что-то. Вам следует подождать как можно дольше, чтобы создать N или N_g и связать получившийся объект N (или подкласс N) с деревом.
"Кроме того, иногда мне нужно обрезать все G: s и генерировать больше N: s, прежде чем, возможно, генерировать еще несколько G: s."
Fine. Вы идете по дереву, заменяя N_g экземплярами на N экземпляров, чтобы «обрезать». Вы идете по дереву, заменяя N экземпляров на N_g, чтобы создать новое / другое поддерево G.