I agree that nether way around seems inherently better than the other, so it would depend on the specifics of that project.
> The same instruction set would apply if it were knight attacking mage or mage attacking knight.
Not necessarily! For example, a mage might cast long-range magic, whilst a knight would only have melee attacks. Where we make this distinction can affect our understanding, maintaining and extending of the code.
Standard OOP practice would use an inheritance hierarchy to handle this difference, e.g. a `Character` class could have `RangedCharacter` and `MeleeCharacter` subclasses whose `attack` method handles the details of those sorts of attack; then `Knight` and `Mage` subclasses those, to specialise the parameters. The major problem with this is that we can't model multiple distinctions in this way; for example knights and mages might both be "noble", whilst trolls and imps are "evil". If we want `Knight` to inherit from both `MeleeCharacter` and `NobleCharacter` we'd need multiple inheritance, which is where things get tricky.
More recent trends favour composition rather than inheritance, e.g. passing in an `Attack` argument during construction, with `RangedAttack` and `MeleeAttack` subclasses. Separate objects can be passed in for the `Noble`/`Evil` distinction. Of course, these are just standalone functions with more boilerplate ;)
Mixins are somewhere inbetween, where we avoid some of the boilerplate chaperones ( https://steve-yegge.blogspot.com/2006/03/execution-in-kingdo... )
The CLOS approach, mentioned by others in these comments, lets us define verbs like `attack` outside of any particular class or mixin. We can then define it case-wise based on its arguments, like `attack(RangedCharacter, Character)` and `attack(MeleeCharacter, Character)`.