Created
December 15, 2012 19:07
-
-
Save mpusz/4298248 to your computer and use it in GitHub Desktop.
[OOD] Visitor design pattern used to build Spreadsheet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// author: Mateusz Pusz | |
// | |
#include <functional> | |
#include <memory> | |
#include <vector> | |
#include <iostream> | |
#include <cassert> | |
#include <stdexcept> | |
#include <boost/noncopyable.hpp> | |
// -------------------- TOOLS --------------------- | |
template<typename T, typename ...Args> | |
inline std::unique_ptr<T> make_unique(Args&&... args) | |
{ | |
return std::unique_ptr<T>(new T{std::forward<Args>(args)...}); | |
} | |
// -------------------- FRAMEWORK --------------------- | |
namespace spreadsheet { | |
template<typename T> class CSpreadsheet; | |
template<typename T> class CVisitor; | |
// -------------------- EXPRESSIONS --------------------- | |
template<typename T> | |
struct CExpression : private boost::noncopyable { | |
virtual ~CExpression() = default; | |
virtual void Process(CVisitor<T> &visitor) const = 0; | |
}; | |
template<typename T, class Child> | |
struct CExpressionCRTP : CExpression<T> { | |
void Process(CVisitor<T> &visitor) const override; | |
}; | |
template<typename T> | |
struct CExpressionConstant : CExpressionCRTP<T, CExpressionConstant<T> > { | |
const T value; | |
CExpressionConstant(T value): value(std::move(value)) {} | |
}; | |
template<typename T, int Type> | |
struct CExpressionBinary : CExpressionCRTP<T, CExpressionBinary<T, Type> > { | |
const std::unique_ptr<CExpression<T> > left; | |
const std::unique_ptr<CExpression<T> > right; | |
CExpressionBinary(std::unique_ptr<CExpression<T> > left, std::unique_ptr<CExpression<T> > right): | |
left(move(left)), right(move(right)) {} | |
}; | |
// I am too lazy to inherit that manually and have to rewrite all constructors and Process() later on :-) | |
enum TExpressionBinaryType { ADD, SUBTRACT, MULTIPLY, DIVIDE }; | |
template<typename T> using CExpressionAdd = CExpressionBinary<T, TExpressionBinaryType::ADD>; | |
template<typename T> using CExpressionSubtract = CExpressionBinary<T, TExpressionBinaryType::SUBTRACT>; | |
template<typename T> using CExpressionMultiply = CExpressionBinary<T, TExpressionBinaryType::MULTIPLY>; | |
template<typename T> using CExpressionDivide = CExpressionBinary<T, TExpressionBinaryType::DIVIDE>; | |
template<typename T> | |
struct CExpressionCell : CExpressionCRTP<T, CExpressionCell<T> > { | |
const CSpreadsheet<T> &spreadsheet; | |
const std::size_t row; | |
const std::size_t col; | |
CExpressionCell(const CSpreadsheet<T> &spreadsheet, std::size_t row, std::size_t col): | |
spreadsheet(spreadsheet), row(row), col(col) {} | |
}; | |
// -------------------- SPREADSHEET --------------------- | |
template<typename T> | |
class CSpreadsheet { | |
std::vector<std::vector<std::unique_ptr<CExpression<T> > > > _cells; | |
public: | |
CSpreadsheet(std::size_t rows, std::size_t cols): _cells(rows) | |
{ | |
for(auto &c : _cells) | |
c.resize(cols); | |
} | |
void Expression(std::size_t row, std::size_t col, std::unique_ptr<CExpression<T> > expr) | |
{ | |
_cells.at(row).at(col) = std::move(expr); | |
} | |
const CExpression<T> &Expression(std::size_t row, std::size_t col) const | |
{ | |
auto &cell = _cells.at(row).at(col); | |
if(cell) | |
return *cell; | |
throw std::runtime_error("No expression in cell(row: " + std::to_string(row) + ", " + std::to_string(col) + ")"); | |
} | |
T operator()(std::size_t row, std::size_t col) const; | |
void Print() const; | |
}; | |
// -------------------- VISITORS --------------------- | |
// Proposed interface hides not only dispatching but also visitors usage from the user. | |
// BTW dispatching is slightly different for 2 implemented cases to show power of that solution | |
template<typename T> | |
class CVisitor : boost::noncopyable { | |
public: | |
void Run(const CExpression<T> &) { throw std::runtime_error("Visitor dispatching failed"); } | |
virtual void Run(const CExpressionConstant<T> &expr) = 0; | |
virtual void Run(const CExpressionAdd<T> &expr) = 0; | |
virtual void Run(const CExpressionSubtract<T> &expr) = 0; | |
virtual void Run(const CExpressionDivide<T> &expr) = 0; | |
virtual void Run(const CExpressionMultiply<T> &expr) = 0; | |
virtual void Run(const CExpressionCell<T> &expr) = 0; | |
}; | |
// Obtains the value of the expression | |
// To use that visitor just call Value(CExpression<T>) | |
template<typename T> | |
class CVisitorValue : public CVisitor<T> { | |
T _value; | |
// so templates cannot be virtual methods but they can use templates inside :-) | |
template<int Type, typename OPER> | |
void Run(const CExpressionBinary<T, Type> &expr, OPER op) | |
{ | |
_value = op(CVisitorValue<T>(*expr.left), CVisitorValue<T>(*expr.right)); | |
} | |
public: | |
CVisitorValue(const CExpression<T> &expr) { expr.Process(*this); } | |
operator T() const { return _value; } | |
void Run(const CExpressionConstant<T> &expr) override { _value = expr.value; } | |
void Run(const CExpressionAdd<T> &expr) override { Run(expr, std::plus<T>()); } | |
void Run(const CExpressionSubtract<T> &expr) override { Run(expr, std::minus<T>()); } | |
void Run(const CExpressionDivide<T> &expr) override { Run(expr, std::divides<T>()); } | |
void Run(const CExpressionMultiply<T> &expr) override { Run(expr, std::multiplies<T>()); } | |
void Run(const CExpressionCell<T> &expr) override { _value = expr.spreadsheet(expr.row, expr.col); } | |
}; | |
template<typename T> | |
inline T Value(const CExpression<T> &expr) { return CVisitorValue<T>(expr); } | |
// Dumps expression to any output stream | |
// To use that visitor just pass CExpression<T> to a stream with << operator | |
template<typename T, typename Stream> | |
class CVisitorOStream : public CVisitor<T> { | |
Stream &_stream; | |
// so templates cannot be virtual methods but they can use templates inside :-) | |
template<int Type, typename OPER> | |
void Run(const CExpressionBinary<T, Type> &expr, OPER &&op) | |
{ | |
expr.left->Process(*this); | |
_stream << std::forward<OPER>(op); | |
expr.right->Process(*this); | |
} | |
public: | |
CVisitorOStream(const CExpression<T> &expr, Stream &stream): _stream(stream) { expr.Process(*this); } | |
operator Stream&() const { return _stream; } | |
void Run(const CExpressionConstant<T> &expr) override { _stream << expr.value; } | |
void Run(const CExpressionAdd<T> &expr) override { Run(expr, "+"); } | |
void Run(const CExpressionSubtract<T> &expr) override { Run(expr, "-"); } | |
void Run(const CExpressionDivide<T> &expr) override { Run(expr, "/"); } | |
void Run(const CExpressionMultiply<T> &expr) override { Run(expr, "*"); } | |
void Run(const CExpressionCell<T> &expr) override { _stream << "[" << expr.row << "," << expr.col << "]"; } | |
}; | |
template<typename Stream, typename T> | |
inline Stream &operator<<(Stream &stream, const CExpression<T> &expr) | |
{ | |
return CVisitorOStream<T, Stream>(expr, stream); | |
} | |
// -------------------- DETAILS --------------------- | |
// implementation that couldn't be provided before because of type dependencies | |
template<typename T, class Child> | |
inline void CExpressionCRTP<T, Child>::Process(CVisitor<T> &visitor) const | |
{ | |
visitor.Run(static_cast<const Child&>(*this)); | |
} | |
// CVisitorValue usage | |
template<typename T> | |
inline T CSpreadsheet<T>::operator()(std::size_t row, std::size_t col) const | |
{ | |
return Value(Expression(row, col)); | |
} | |
// CVisitorOStream usage | |
template<typename T> | |
void CSpreadsheet<T>::Print() const | |
{ | |
for(size_t row=0; row<_cells.size(); ++row) | |
for(size_t col=0; col<_cells[row].size(); ++col) | |
if(_cells[row][col]) | |
std::cout << "[" << row << "," << col << "] -> " | |
<< *_cells[row][col] << " = " | |
<< operator()(row, col) << "\n"; | |
} | |
} | |
// -------------------- USAGE --------------------- | |
using Value = double; | |
constexpr std::size_t ROWS = 10; | |
constexpr std::size_t COLS = 10; | |
using CSpreadsheet = spreadsheet::CSpreadsheet<Value>; | |
using CExpressionConstant = spreadsheet::CExpressionConstant<Value>; | |
using CExpressionAdd = spreadsheet::CExpressionAdd<Value>; | |
using CExpressionSubtract = spreadsheet::CExpressionSubtract<Value>; | |
using CExpressionMultiply = spreadsheet::CExpressionMultiply<Value>; | |
using CExpressionDivide = spreadsheet::CExpressionDivide<Value>; | |
using CExpressionCell = spreadsheet::CExpressionCell<Value>; | |
int main() | |
{ | |
try { | |
CSpreadsheet sheet(ROWS, COLS); | |
// sheet(ROWS, COLS); // should throw | |
// sheet(1, 1); // should throw | |
sheet.Expression(0, 0, make_unique<CExpressionConstant>(5.0)); | |
assert(sheet(0, 0) == 5); | |
sheet.Expression(0, 1, make_unique<CExpressionAdd>(make_unique<CExpressionConstant>(10.0), | |
make_unique<CExpressionCell>(sheet, 0, 0))); | |
assert(sheet(0, 1) == 15); | |
sheet.Expression(0, 2, make_unique<CExpressionDivide>(make_unique<CExpressionCell>(sheet, 0, 1), | |
make_unique<CExpressionCell>(sheet, 0, 0))); | |
assert(sheet(0, 2) == 3); | |
// override last cell and set an expression for not filled one | |
sheet.Expression(0, 2, make_unique<CExpressionCell>(sheet, 0, 3)); | |
// sheet(0, 2); // should throw | |
sheet.Expression(0, 3, make_unique<CExpressionMultiply>(make_unique<CExpressionConstant>(2.0), | |
make_unique<CExpressionSubtract>(make_unique<CExpressionCell>(sheet, 0, 1), | |
make_unique<CExpressionCell>(sheet, 0, 0)))); | |
assert(sheet(0, 3) == 20); | |
// check now | |
assert(sheet(0, 2) == sheet(0, 3)); | |
// print expressions | |
sheet.Print(); | |
} | |
catch(const std::exception &ex) { | |
std::cerr << "ERROR: " << ex.what() << "\n"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment