One of the easiest ways to ensure loose coupling between objects in C++ is to use abstract base classes to define an interface, and then use that interface in other classes.
Let’s define a simple FilterInterface
abstract base class that defines the interface for subsequent filter classes, which will then be used as a component of an ADC class.
#include <iostream> #include <vector> class FilterInterface { public: virtual ~FilterInterface() {} virtual void reset() = 0; virtual void update(int a, int b) = 0; virtual int get_a() = 0; virtual int get_b() = 0; protected: int a_{1}; int b_{1}; }; class AverageFilter : public FilterInterface { public: void reset() override final{ a_ = 0; b_ = 0; } void update(int a, int b) override final { a_ = a; b_ = b; } int get_a() override final { return a_; } int get_b() override final { return b_; } }; class ADC { public: ADC(FilterInterface& interface) : interface(interface) { interface.reset(); } void process() { interface.update(10, 20); } std::vector<int> get_values() { std::vector<int> vec; vec.push_back(interface.get_a()); vec.push_back(interface.get_b()); return vec; } protected: FilterInterface& interface; }; int main() { AverageFilter filter; std::cout<<"filter.a = "<<filter.get_a()<<", filter.b = "<<filter.get_b()<<std::endl; std::cout<<"Constructing adc object"<<std::endl; ADC adc(filter); std::vector<int> vec = adc.get_values(); std::cout<<"adc.interface.a = "<<vec[0]<<", adc.interface.b = "<<vec[1]<<std::endl; std::cout<<"calling process method"<<std::endl; adc.process(); vec = adc.get_values(); std::cout<<"adc.interface.a = "<<vec[0]<<", adc.interface.b = "<<vec[1]<<std::endl; }
First, we declare the abstract base class FilterInterface
by declaring pure virtual functions. We then inherit from it to create the derived class AverageFilter. The class ADC then takes in a reference to something that is FilterInterface
like, or at least uses the interface defined by it. This allows us to have the ADC class decoupled from the implementation details of child classes of FilterInterface
, and we can pass in a references to other child classes of it. This way, if we decide we need to change the filter that’s used in ADC
, and want to use, let’s say some class we called SavitzkyGolayFilter
, it’s easy peasy.
If we compile and run the executable above we get the following output:
filter.a = 1, filter.b = 1 Constructing adc object adc.interface.a = 0, adc.interface.b = 0 calling process method adc.interface.a = 10, adc.interface.b = 20
Which allows us to see the polymorphic business going on pretty easily.