Afaik, OOP (for example in Java) still need which method can be override, which final, which abstract method(/class).
If the author didn't set those info, nothing for lib's users able to override or do anything the original lib. Actually you're incorrect that a proc can be redefined across module because each proc defined with different module has symbol name as module_name.proc_name, so if there's two procs that has same arity and parameters types, compiler will report the error unless you call it full the proc symbol. This is how you do it with closure #heroes.nim type Hero* = ref object of RootObj ImplementationError* = object of Exception method kill*(self, victim: Hero, howItKilled: proc(casualty: Hero) = nil) = if howItKilled.isNil: raise newException(ImplementationError, "Hero descendants should describe how they're killed") else: howItKilled victim #jedi.nim import heroes type OB1* = ref object of Hero Yoda* = ref object of Hero method kill*(self: Hero, ob1: OB1, howItKilled = (proc(victim: OB1) = echo "Obi Wan Kenobi was Killed")) = howItKilled ob1 method kill*(self: Hero, yoda: Yoda, howItKilled = (proc(victim: Yoda) = echo "Obi Wan Kenobi was Killed")) = howItKilled yoda #main.nim import heroes import jedi let darthVader = new Hero yoda = new Yoda try: darthVader.kill(cast[Hero](yoda), howItKilled=nil) except ImplementationError: echo getCurrentExceptionMsg() darthVader.kill yoda # call with default defined closure darthVader.kill(yoda, howItKilled=(proc(yoda: Yoda) = echo "Yoda may not be killed"))