Single Responsibility Principle – Understanding Inheritance in C++

cinheritanceobject-orientedobject-oriented-design

I have the following interface:

class IHittable
{
    virtual Intersections intersects(const Ray & ray) = 0
}

which will be implemented by various geometric objects(Spheres, Triangles etc).
Now the class World will look like this:

class World
{
  public:
    Intersection intesects(const Ray & ray)
    {
      auto intersections = Intersections();
      for(auto object : objects)
      {
        auto xs = object.intersection();
        if(xs.size() > 0)
        {
          intersections.append(xs);
        }
      }
      return intersections;
   }
  private:
     std::vector<std::shared_ptr<IHittable>> objects;
}

My question is, should the class World also implement the IHittable interface? because its member looks the same and does exactly the same thing? How bad is it if I don't do that?

A second more generall question if this is allowed:
When should I make a function free standing and when not, in my case the function intersects inside the World class could be easily made free standing just with one extra argument. I don't know how to think about such cases.

Am I going against the "Single Responsibility Principle" if Intersects is a member of World?

Best Answer

My question is, should the class World also implement the IHittable interface? because its member looks the same and does exactly the same thing?

It would be a reasonable choice to make World implement IHittable because it gives you the option to add an instance of World to another World (or generalize the idea of World and rename it Container). This would be an example of the Composite pattern (https://en.wikipedia.org/wiki/Composite_pattern).

How bad is it if I don't do that?

If you never intend to put a world in a world, there is no harm at all to not implement IHittable. After all your design is not about getting it right, but what serves your use case the best. If your code will never treat a World object as an IHittable, then World shouldn't implement that interface.

A second more generall question(subquestion) if this is allowed: When should I make a function free standing and when not, in my case the function intersects inside the World class could be easily made free standing just with one extra argument. I don't know how to think about such cases.

This ends up being a question about your object boundaries and abstraction. There is no easy answer to these types of question, since it takes some experience to get to the right abstractions specific use case. But I give you some things to consider: If intersects is an instance method, you can make World implement IHittable If you want to make intersects a static method that takes both a Ray and a World then you need to be able to access the list of IHittable objects the world contains When you make objects public to allow access from an static method, your World object ends up just being a data structure. That works but you end up losing all the aspects of object oriented programming (whether that is a good thing or a bad thing).