my "engine" uses different object representations: object are added to the world using a scene graph but that is not used for rendering as it would not be the most efficient way. Rather, after the scene is complete, a "SceneManager" examines the graph and computes the most efficient way to render it. As it has been said, objects are grouped according to materials, geometry used, rendering order and other properties. This scene manager returns a list of "commands" that the rendering loop executs. Commands can be of various types, i.e.: generate a shadow map, activate blending, render objects and so on.
Another thing that I've been doing is separating the object class from the geometry class. In my engine, the object represents the high-level properties of a mesh such as its local position, rotation, etc. (local because the absolute values are obtained according to the scene graph). Whereas the geometry class contains the actual vertex/index buffers. There is only one geometry instance for each unique 3D object in the world.
This helps further improve the rendering efficiency. After each object has been grouped into materials then I further group each one of these objects according to the geometry used. Then for each Material/Geometry couple I issuse a "Render Command" to render all the objects that use the same materials and reference geometry. This way there will be only one setVB/IB command per group. This also helps with hardware instancing: if a material supports it, then I just use the list of object instances to compute an instance buffer.