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> | |
// -------------------- 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)...}); | |
} | |
class CNonCopyable { | |
public: | |
CNonCopyable() = default; | |
CNonCopyable(const CNonCopyable &) = delete; | |
CNonCopyable &operator=(const CNonCopyable &) = delete; | |
}; | |
// -------------------- FRAMEWORK --------------------- | |
namespace spreadsheet { | |
template<typename T> class CSpreadsheet; | |
namespace detail { | |
template<typename T> class CVisitor; | |
} | |
// -------------------- EXPRESSIONS --------------------- | |
template<typename T> | |
struct CExpression : private CNonCopyable { | |
virtual ~CExpression() = default; | |
virtual void Process(detail::CVisitor<T> &visitor) const = 0; | |
}; | |
template<typename T, class Child> | |
struct CExpressionCRTP : CExpression<T> { | |
void Process(detail::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; | |
}; | |
namespace detail { | |
// -------------------- VISITORS --------------------- | |
// Proposed interface hides not only visitors dispatching but also whole visitors usage from the user. | |
// It is done like that because if a new expression type will be provided in the future visitor abstract | |
// class interface will have to change and eventual children implemented by clients will stop to compile. | |
// BTW dispatching is slightly different for 2 implemented cases to show the power of that solution | |
template<typename T> | |
class CVisitor : CNonCopyable { | |
public: | |
void Run(const CExpression<T> &) = delete; // 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; | |
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 { _value = CVisitorValue<T>(*expr.left) + CVisitorValue<T>(*expr.right); } | |
void Run(const CExpressionSubtract<T> &expr) override { _value = CVisitorValue<T>(*expr.left) - CVisitorValue<T>(*expr.right); } | |
void Run(const CExpressionDivide<T> &expr) override { _value = CVisitorValue<T>(*expr.left) / CVisitorValue<T>(*expr.right); } | |
void Run(const CExpressionMultiply<T> &expr) override { _value = CVisitorValue<T>(*expr.left) * CVisitorValue<T>(*expr.right); } | |
void Run(const CExpressionCell<T> &expr) override { _value = expr.spreadsheet(expr.row, expr.col); } | |
}; | |
// 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) | |
{ | |
_stream << "("; | |
expr.left->Process(*this); | |
_stream << std::forward<OPER>(op); | |
expr.right->Process(*this); | |
_stream << ")"; | |
} | |
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 << "]"; } | |
}; | |
} | |
// User interface for expressions wrapping visitors usage | |
template<typename T> | |
inline T Value(const CExpression<T> &expr) | |
{ | |
return detail::CVisitorValue<T>(expr); | |
} | |
template<typename Stream, typename T> | |
inline Stream &operator<<(Stream &stream, const CExpression<T> &expr) | |
{ | |
return detail::CVisitorOStream<T, Stream>(expr, stream); | |
} | |
// -------------------- OTHER --------------------- | |
// implementation that couldn't be provided before because of type dependencies | |
template<typename T, class Child> | |
inline void CExpressionCRTP<T, Child>::Process(detail::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