These are common C++ idioms that people follow when interfacing classes together. ## Composition ``` +-----------+ owns (strong) | Car |◆───────> +--------+ | | | Engine | +-----------+ +--------+ | | by-value member or unique_ptr member v fields: Engine e_; // or: std::unique_ptr e_; ``` The idea here is that the whole owns the part. That is, a derived class contains the object directly as a data member, or as a `unique_ptr`. ```c++ class Engine { public: explicit Engine(int horsepower) : hp_(horsepower) {} void start() const noexcept { std::cout << "vroom\n"; } private: int hp_; }; class Car { public: explicit Car(int horsepower) : engine_(horsepower) {} void start_car() const { engine_.start(); std::cout << "car has started!\n"; } private: Engine engine_ }; // You can also do something similar using a unique_ptr class CarUnique { public: explicit Car(int horsepower) : engine_(std::make_unique(horsepower)) {} void start_car() const { engine_->start(); std::cout << "car has started!\n"; } private: std::unique_ptr engine_; }; ``` ## Aggregation ``` +-----------+ non-owning raw ptr +-----------+ | Team | ------------------------------> | Player | +-----------+ +-----------+ ``` The idea here is that the object has reference to a part it needs, but does not own the part itself. This is to avoid accidental lifetime extensions. ```c++ class Player { public: Player(std::string name) : name_(name) {} void intro() { std::cout << "I am" << name << std::endl; } private: std::string name_; }; class Team { public: Team() = default; void add_player(Player* p) { players.push_back(p); } void roll_call() { for (auto* p: players_) p->intro(); } private: std::vector players; } // can also do a similar thing with weak_ptrs class TeamWeakPtr { public: Team() = default; void add_player(const std::shared_ptr& p) { players.push_back(p); // this implicitly will store weak_ptrs } void roll_call() { for (const auto& p : players_) { if (auto p_ptr = p.lock()) { p_ptr->intro(); } } } private: std::vector> players; } int main() { Team team(); TeamWeakPtr team_weak_ptr(); // for regular Player alice("alice"); Player bob("bob"); team.add_player(alice); team.add_player(bob); team.roll_call(); // for weak_ptr auto alice = std::make_shared("alice"); auto bob = std::make_shared("bob"); team.add_player(alice); team.add_player(bob); team.roll_call(); } ``` ## Dependency Injection ``` +-------------------+ | Storage (IF) |<-- virtual API +-------------------+ ^ ^ implements | | +-----------------+---+ +---+-----------------+ | InMemoryStorage | | SqlStorage | +---------------------+ +---------------------+ injected (owns or borrows) +--------------------+ | Repository |----->[ Storage, either InMemoryStorage or SqlStorage ] (unique_ptr or reference) +--------------------+ ``` This is the idea of having a class accept a pre-built object. This differs for composition because the object it depends on is initialized in its constructor. #### Example using Static [[Polymorphism]] ```c++ class MapStorage { std::map kv_; public: void put(const std::string& k, std::string v) { kv_[k]=std::move(v); } std::string get(const std::string& k) const { auto it = kv_.find(k); return (it==kv_.end()) ? "" : it->second; } }; template class RepoT { StoragePolicy storage_; // value or reference wrapper public: RepoT() = default; explicit RepoT(StoragePolicy s) : storage_(std::move(s)) {} void save_user(const std::string& id, std::string name) { storage_.put("user:"+id, std::move(name)); } std::string load_user(const std::string& id) const { return storage_.get("user:"+id); } }; int main() { RepoT repo; // choose policy at compile time repo.save_user("7", "Alice"); std::cout << repo.load_user("7") << "\n"; } ``` You used this in your templated test classes! To make base test nodes that handle any ros publisher handling any sort of message type! #### Example using Dynamic [[Polymorphism]] ```c++ class Storage { public: virtual ~Storage() = default; virtual void put(const std::string&, const std::string&) = 0; virtual std::string get(const std::string&) const = 0; }; // I can implement different storage options and make repository use any of them! class InMemoryStorage : public Storage { std::map kv_; public: void put(const std::string& k, const std::string& v) override { kv_[k]=v; } std::string get(const std::string& k) const override { auto it = kv_.find(k); return (it==kv_.end()) ? "" : it->second; } }; class Repository { std::unique_ptr storage_; // HERE WE ARE POINTING TO THE BASE OBJECT!! public: explicit Repository(std::unique_ptr s) : storage_(std::move(s)) {} void save_user(const std::string& id, const std::string& name) { storage_->put("user:"+id, name); } std::string load_user(const std::string& id) const { return storage_->get("user:"+id); } }; int main() { auto mem_storage = std::make_unique(); Repository repo{std::move(mem_storage)}; // inject impl repo.save_user("88", "Eddy"); std::cout << repo.load_user("88") << "\n"; } ``` ## Factory Generally refers to the idea of having a class or function be responsible for producing the right object with the right internal composition for the end user of the library based on some requirements. ### Simple Factory Most common, accepts parameters and builds an object accordingly. ```c++ class Renderer { public: virtual ~Renderer() = default; virtual void draw() const = 0; }; class OpenGLRenderer : public Renderer { public: void draw() const override { std::cout << "OpenGL draw\n"; } }; class VulkanRenderer : public Renderer { public: void draw() const override { std::cout << "Vulkan draw\n"; } }; class RendererFactory { public: static std::unique_ptr create(const std::string& api) { if (api == "opengl") return std::make_unique(); if (api == "vulkan") return std::make_unique(); throw std::invalid_argument("unknown api"); } }; int main() { auto r = RendererFactory::create("vulkan"); r->draw(); } ``` ### Factory Method No central switch like the simple factory. Instead builds based on defined subclasses using the things you want to use. More of a complete composer of parts. ```c++ class Renderer { public: virtual ~Renderer() = default; virtual void draw() const = 0; }; class OpenGLRenderer : public Renderer { public: void draw() const override { std::cout << "OpenGL\n"; } }; class VulkanRenderer : public Renderer { public: void draw() const override { std::cout << "Vulkan\n"; } }; class App { public: virtual ~App() = default; void run() const { auto r = create_renderer(); r->draw(); } private: virtual std::unique_ptr create_renderer() const = 0; // factory method }; class GameApp : public App { private: std::unique_ptr create_renderer() const override { return std::make_unique(); } }; class CadApp : public App { private: std::unique_ptr create_renderer() const override { return std::make_unique(); } }; int main() { std::unique_ptr app = std::make_unique(); app->run(); } ``` ### Abstract Factory More of a grouping pattern. Creates families that must match. ```c++ class Button { public: virtual ~Button() = default; virtual void paint() const = 0; }; class Checkbox { public: virtual ~Checkbox() = default; virtual void paint() const = 0; }; class DarkButton : public Button { public: void paint() const override { std::cout << "Dark Button\n"; } }; class DarkCheckbox : public Checkbox { public: void paint() const override { std::cout << "Dark Checkbox\n"; } }; class LightButton : public Button { public: void paint() const override { std::cout << "Light Button\n"; } }; class LightCheckbox : public Checkbox { public: void paint() const override { std::cout << "Light Checkbox\n"; } }; class WidgetFactory { public: virtual ~WidgetFactory() = default; virtual std::unique_ptr