We (on 254) usually just end up writing a bunch of boiler plate code to do this, but I think #3 is a solid way of solving this. Personally I don't make an abstraction until I have at least 3 things that need it (you know the whole bit about premature optimization...). If I had to, I'd make Subsystems implement Interfaces like "Extendable" or "Controllable" then make commands that handle that.
125 did this last year a little differently by subclassing subsystem, then building Commands that could control those types of subsystems.
https://github.com/kreeve/Nutrons201...Subsystem.java