Hide implementation details.
Abstract common concepts into base classes.
Keep coupling to a minimum: a class may depend on several basic types, but should depend on few of its peers.
Keep cohesion to a maximum: a class should be focused on a single concept, embodied in the class's name, and should depend on as little data as possible to accomplish its goals.
Remember that there are several ways to tie two classes together, such as inheritance, aggregation, and event handling.
Always aim for reusability. The more easily a class can be lifted from its current project and used in another project, the more reusable it is. A good example is that of a file system: A FileSystemDriver that depends on peer drivers and a static I/O scheduler would be difficult to reuse. A FileSystem that is self-contained and depends on an abstract scheduler supplied in its constructor is easy to reuse.
Strike a balance between virtual/abstract methods and final methods. Sealed types and final methods are fast, but cannot be overridden. Virtual methods are slower, but can be overridden.