1° The player character is a self-contained model object (that is, it represents the internal state of the player character, without any kind of relationship to the outside world).
2° The zone is a model object representing the structure of the level.
3° The jumpRule is a model rule object which is used for having the player character jump inside a zone.
So, player.jump() does not check whether there's something below: it just jumps. The zone doesn't need to know whether a player exists within itself (it may only know, for instance, that a moving object is present, but not that it is a player). It does provide, however, an accessor zone.isSolid(position) to determine whether ground is present.
And then, your jump rule does the following:
class jumpRule{ public override void jump(Player p, Zone z) { if (z.isSolid(p.position().underfoot())) { p.jump(); } }}
This way, neither zone nor player need to know about each other, and you can replace a jump rule with another (double-jumping, wall-jumping) as you wish, even at runtime.