get_posts() is a thin wrapper over WP_Query, so they are usually described as interchangeable. They are not: get_posts() defaults suppress_filters to true, which disables every posts_* filter for that query.
// plugin filters on posts_where / posts_join do NOT run
get_posts( array( 'post_type' => 'product' ) );
// they do run
new WP_Query( array( 'post_type' => 'product' ) );
// or opt back in explicitly
get_posts( array( 'post_type' => 'product', 'suppress_filters' => false ) );
This is why a multilingual plugin returns the wrong language from one call and the right one from another, in the same file. It also defaults ignore_sticky_posts to true and posts_per_page to 5 rather than the site setting. Neither is wrong; both are surprising if you assumed the two APIs were the same thing.