Skip to content

OrderOfInterestRule

djewsbury edited this page Jan 27, 2015 · 1 revision

#"Order of Interest" rule

Consider following the order of interest rule.

This rule dictates the order of the declarations inside of a class definition. It says this:

  • member functions that are of the most interest to the client should be near the top of the class definition
  • member functions that less interesting to the client should be near the bottom.

##Details

When we say "of interest to the client", we mean the programmer than will use the client. Note that this is not the same as the implementor of the class. When deciding on the order, we should be considering the viewpoint of a programmer using the completed class (not our own viewpoint as we create the class).

We want class definitions to function as documentation for that class. That means formatting the definition to make it easy for clients to gather information quickly. Well formatted class definitions give an overall impression of the intentions and functionality of a class, even at the first glance.

There are a few consequences to this rule:

  • the public section will generally be first, followed by protected and finally private
  • this is because the public interface is what is used by most clients.
  • the protected interface is only used by clients building derived classes
  • and the private members are not used by clients are all
  • to clients: public is more interesting than protected is more interesting than private
  • constructors, destructors and assignment operators will generally come at the end of the public part. Normally these can more or less be taken for granted.

Consider these examples:

namespace BadExamples
{
  /// <summary>A class that is difficult to read</summary>
  /// There's some great examples of this the MSVC's STL
  /// implementation. Take a look at how the STL containers
  /// and iterators are implemented. There are reasons why
  /// that code may appear like that, but they still make
  /// good examples of obfuscated, unclear class definitions.
  class PoorlyFormatted
  {
  private:
    class NoOneCaresAboutThis;
    typedef std::unique_ptr<NoOneCaresAboutThis> ExtraChaff;
    ExtraChaff iveForgottenAlready;
    static const unsigned s_maxItems = 512;

  public:
    void OnDeviceReset();
    void OnShutdown();
    bool HasItem(uint64 hash) const;
    void LogContents() const;

    PoorlyFormatted();
    ~PoorlyFormatted();
  private:
    PoorlyFormatted(const PoorlyFormatted&);
    PoorlyFormatted& operator=(const PoorlyFormatted&);

  public:
    static const uint64 s_invalid = ~0x0ull;

  private:
    friend class Texture;
    void Register(uint64 hash, std::shared_ptr<Texture> texture);
    void Deregister(uint64 hash);

  public:
    static uint64 AsHash(const char name[]);

  private:
    std::vector<std::pair<uint64, std::shared_ptr<Texture> texture>> _registeredItems;

  public:
    std::shared_ptr<Texture> GetTexture(uint64 hash) const;
  };
}

What is this class? What does it do? It's not very clear. It's not until the very end, when we see GetTexture that we start to understand the intention of this class. It's some sort of texture manager.

We should reorder this class according to these principles:

  1. GetTexture is the most important method to clients. It defines the purpose of the class, and it is the most commonly used. It should be first.
  2. AsHash is also frequently used, so it should also be earlier
  3. There are some situational methods: OnDeviceReset(), OnShutdown(), LogContents()
  • these are public methods, but they are only intended for use in very specific situations
  1. There is a special interface that should only be used by a single class: Register() & Deregister().
  • This is hidden from almost all clients, it's expected that only the Texture class should use it. So it should be late in the definition
  1. There are some private members and implementation details. They are only required by the implementation, so they should definitely be last.

Here is a reordered version:

namespace GoodExamples
{
  class TextureManager
  {
  public:
      // defining interface
    std::shared_ptr<Texture> GetTexture(uint64 hash) const;
    static uint64 AsHash(const char name[]);

      // utilities & support interface
    bool HasItem(uint64 hash) const;
    void LogContents() const;
    static const uint64 s_invalid = ~0x0ull;

      // situational interface
    void OnDeviceReset();
    void OnShutdown();

    TextureManager();
    ~TextureManager();

  private:
      // restricted interface for Texture class
    friend class Texture;
    void Register(uint64 hash, std::shared_ptr<Texture> texture);
    void Deregister(uint64 hash);

      // private implementation details
    std::vector<std::pair<uint64, std::shared_ptr<Texture> texture>> _registeredItems;
    class NoOneCaresAboutThis;
    std::unique_ptr<NoOneCaresAboutThis> iveForgottenAlready;
    static const unsigned s_maxItems = 512;

    TextureManager& operator=(const TextureManager&);
    TextureManager(const TextureManager&);
  };
}

##Conclusion

There aren't always clear rules as to the best ordering within a class. Sometimes there isn't a single best ordering.

But the most important thing is to remember this:

  • Don't format your implementations for yourself. Format them for your clients! (ie, the programmers that will use your class)
  • well written class definitions can serve as documentation. Often a good header can be easier to read than a auto-generated html file.