Skip to content

Instantly share code, notes, and snippets.

@unixnme
Last active May 10, 2020 02:25
Demo of runtime-implementation design
#include <iostream>
#include <memory>
/*
* This code provides an example design pattern to achieve the following:
*
* We want to implement Container class using two different implementations
* We further want to select its implementation during runtime
* We also want to be able to do the same for any classes derived from Container
* We even want to be able to templatize on Container class
*/
/**
* interface that all implementations must bind to
*/
class ContainerInterface {
public:
virtual ~ContainerInterface() = default;
virtual void identify() const = 0;
virtual void push_back(int) = 0;
virtual void pop_back() = 0;
virtual size_t size() const = 0;
virtual int &front() = 0;
virtual int &back() = 0;
};
/**
* one type of implementation
*/
class Array : public ContainerInterface {
public:
Array() = default;
void identify() const override { std::cout << "array" << std::endl; }
void push_back(int x) override { /* ... */ }
void pop_back() override { /* ... */ }
size_t size() const override { /* ... */ }
int &front() override { /* ... */ }
int &back() override { /* ... */ }
private:
/* ... */
};
/**
* another type of implementation
*/
class List : public ContainerInterface {
public:
List() = default;
void identify() const override { std::cout << "list" << std::endl; }
void push_back(int x) override { /* ... */ }
void pop_back() override { /* ... */ }
size_t size() const override { /* ... */ }
int &front() override { /* ... */ }
int &back() override { /* ... */ }
private:
/* ... */
};
/**
* a concrete container taking one implementation
*/
class Container : public ContainerInterface {
public:
explicit Container(std::unique_ptr<ContainerInterface> impl)
: impl_{std::move(impl)} {}
void identify() const override { impl_->identify(); }
void push_back(int x) override { impl_->push_back(x); }
void pop_back() override { impl_->pop_back(); }
size_t size() const override { return impl_->size(); }
int &front() override { return impl_->front(); }
int &back() override { return impl_->back(); }
private:
std::unique_ptr<ContainerInterface> impl_;
};
/**
* Demonstration of templated private inheritance
* Queue is implemented through Container APIs
*/
template<typename C = Container>
class Queue : private C {
public:
explicit Queue(std::unique_ptr<ContainerInterface> impl) :
Container{std::move(impl)} {
C::identify();
}
void push(int x) { /* ... */ }
void pop() { /* ... */ }
const int& top() const { /* ... */ }
size_t size() const { return C::size(); }
bool empty() const { return C::empty(); }
};
/**
* Demonstration of public inheritance
* AdvancedContainer extends Container APIs
* but its implementations should only rely on Container APIs
* and should not depend on implementation specific of array or list
*/
class AdvancedContainer : public Container {
public:
explicit AdvancedContainer(std::unique_ptr<ContainerInterface> impl) :
Container{std::move(impl)} {}
bool empty() const { return size() == 0; }
};
int main(int argc, const char** argv) {
// two copies of implementations depending on user input, determined during run-time
std::unique_ptr<ContainerInterface> impl1, impl2;
if (std::strcmp(argv[1], "array") == 0) {
impl1 = std::unique_ptr<ContainerInterface>(new Array);
impl2 = std::unique_ptr<ContainerInterface>(new Array);
}
else {
impl1 = std::unique_ptr<ContainerInterface>(new List);
impl2 = std::unique_ptr<ContainerInterface>(new List);
}
// both Queue and AdvancedContainer implementations are determined during run-time
Queue<Container> queue{std::move(impl1)};
AdvancedContainer advancedContainer{std::move(impl2)};
advancedContainer.identify();
return 0;
}
/*
* run example
* $ ./a.out array
* array
* array
*
* $ ./a.out list
* list
* list
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment