Skip to content

Instantly share code, notes, and snippets.

@mpusz
Created December 15, 2012 19:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mpusz/4298248 to your computer and use it in GitHub Desktop.
Save mpusz/4298248 to your computer and use it in GitHub Desktop.
[OOD] Visitor design pattern used to build Spreadsheet
//
// 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