Wednesday, July 13, 2022

C++ API Design Best Practices - MAKE API LOOSELY COUPLED

Coupling

  • Coupling. A measure of the strength of interconnection between software components, that is, the degree to which each component depends on other components in the system.
  • Cohesion. A measure of how coherent or strongly related the various functions of a single software component are.
  • Good APIs exhibit loose coupling and high cohesion.

Coupling by Name Only

  • If class A only needs to know the name of class B, that is, it does not need to know the size of class B or call any methods in the class, then class A does not need to depend on the full declaration of B.
  • In these cases, you can use a forward declaration for class B, rather than including the entire interface, and so reduce the coupling between the two classes


        class B; // only need to know the name of B

        class A
        {
        public:
            A();
            void SetObject(B *obj);
            B *GetObject() const;

        private:
            B *mObj;
        };
  • In this case, if the associated .cpp file simply stores and returns the Class B object pointer, and restricts any interaction with it to pointer comparisons, then it does not need to #include “B.h” either. In that case, Class  A can be decoupled from the physical implementation of Class B.
  • Use a forward declaration for a class unless you actually need to #include its full definition.

Reducing Class Coupling

  • Prefer declaring a function as a non-member non-friend function rather than as a member function  
        // A.h
        class A
        {
           public:
           std::string GetName() const;

           private:
           std::string mName;
           . . .
        };
         void PrintName(const A&obj);

  • This latter form reduces coupling because the free function PrintName() can only access the public
  • methods of Class A Object
  •  Preferring the non-member non-friend form therefore means that the function is not coupled to the internal details of the class.
  • This technique also contributes to more minimally complete interfaces, where a class contains only the minimal functionality required to implement it, while functionality that is built on top of its public interface is declared outside of the class.
  • It’s worth noting that this happens a lot in the STL, where algorithms such as std::for_each() and std::unique() are declared outside of each container class.

Intentional Redundancy

  • Try to avoid passing the whole object or pass by reference into class when you can, use maps with mapped to string or unique id to the data, rather importing whole class with unnecessary dependencies and includes, this can eliminate coupling. 

Manager Classes 

  • A manager class is one that owns and coordinates several lower-level classes
  • This can be used to break the dependency of one or more classes upon a collection of low-level classes. For example,
  • Use a manager class to reduce coupling to lower-level classes.
  • So that the manager class can remove or add lower-level classes as necessary so that API is loosely coupled
  • Highly coupled API example 
  • After introducing Manager class API becomes loosely coupled
  • With manger class its scalable as well easily we can new lower level classes 


Callbacks, Observers, and Notifications

  • Using techniques like Callbacks, Observers, and Notifications we can reduce coupling within an API related to the problem of notifying other classes when some event occurs.

Callbacks

  • In C/C++, a callback is a pointer to a function within module A that is passed to module B so that B can invoke the function in A at an appropriate time. Module B knows nothing about module A and has no include or link dependencies upon A. 
  • This makes callbacks particularly useful to allow low-level code to execute high-level code that it cannot have a link dependency on. As such, callbacks are a popular technique to break cyclic dependencies in large projects

Observers

  • Callbacks present a solution that works well in plain C programs, although, as just noted, their usage can be convoluted in object-oriented C++ programs without using something like boost::bind. Instead, a more object-oriented solution is to use the concept of observers.
  • This is a software design pattern where an object maintains a list of its dependent objects (observers) and notifies them by calling one of their methods. This is a very important pattern for minimizing coupling in API design.  
 

Notifications

  • Callbacks and observers tend to be created for a particular task, and the mechanism to use them is normally defined within the objects that need to perform the actual callback. 
  • An alternative solution is to build a centralized mechanism to send notifications, or events, between unconnected parts of the system. The sender does not need to know about the receiver beforehand, which lets us reduce the coupling between the sender and the receiver. There are several kinds of notification schemes, but one particularly popular one is signals and slots.
  • Signals can be thought of simply as callbacks with multiple targets (slots). All of the slots for a signal are called when that signal is invoked, or “emitted".
  • A low-level class could therefore create and own a signal. It then allows any unconnected classes to add themselves as slots to this signal. Then the low-level class can emit its signal at any appropriate time and all of the connected slots will be called.
            class A
            {
                        public:
                        void operator()() const
                        {
                        std::cout << "signal called!" << std::endl;
                        }
            };

            // Create an instance of our class A
            A slotA;

            // Create a signal with no arguments and a void return value
            boost::signal<void ()> signal;

            // Connect our slot to this signal
            signal.connect(slotA);

            // Emit the signal and thereby call all of the slots
            signal();

    Reference 

    • API Design for C++ Book by Martin Reddy




          No comments:

          Post a Comment

          LeetCode C++ Cheat Sheet June

          🎯 Core Patterns & Representative Questions 1. Arrays & Hashing Two Sum – hash map → O(n) Contains Duplicate , Product of A...