小区里面加了好几个群,找到了团购的渠道。晚上出去分东西了
6.1 场景
考虑一个例子, 假设你已经有了一个在屏幕上画点的库, 它工作的很好. 现在, 你需要做几何图形的绘制( 线, 矩形等), 你仍然想继续使用原先的画点的库. 这样, 你就需要将几何形状对象适配(Adapt
)到像素上.
首先, 定义领域对象:
1 2 3 4 5 6 7 8
| struct Point { int x,y; } struct Line { Point start, end; }
|
接下来对几何形状做抽象
1 2 3 4 5
| struct VectorObject { virtual std::vector<Line>::iterator begin() = 0; virtual std::vector<Line>::iterator end() = 0; }
|
然后可以定义具体的形状–将它们定义为向量的集合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct VectorRectangle : VectorObject { VecotrRectangle(int x, int y, int width, int height) { lines.emplace_back( Line{ Point{x,y}, Point{x+width, y} }); lines.emplace_back( Line{ Point{x+width,y}, Point{x+width, y+height} }); lines.emplace_back( Line{ Point{x,y}, Point{x, y+height} }); lines.emplace_back( Line{ Point{x,y+height}, Point{x+width, y+height} }); }
std::vector<Line>::iterator begin() overridd{ return lines.begin(); }
std::vector<Line>::iterator end() override{ return lines.end(); } private: std::vector<Line> lines; }
|
现在模型对象建好了. 但是接口呢?
我们的接口是这样的:
1 2 3 4 5 6 7
| void DrawPoints(CPaintDC& dc, std::vector<Point>::interator start, std::vector<Point>::iterator end) { for(auto i=start; i!=end; ++i){ dc.SetPixel(i->x, i->y, 0); } }
|
因此, 无法使用!
6.2 Adapter
考虑我们要画一些矩形:
1 2 3 4
| vector<shared_ptr<VectorObject>> vectorObjects{ make_shared<VectorRectangle>(10,10,100,100), make_shared<VectorRectangle>(20,20,60,60) };
|
为了使用前面的画点的接口, 我们需要把这个矩形结构转换为包含大量要画的点的接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| struct LineToPointAdapter { typedef vector<Point> Points; LineToPointAdapter(Line& line) { ... } virtual Points::iterator begin() { return points.begin(); } virtual Points::iterator end() { return points.end(); } private: Points points; }
|
LineToPointAdapter
的构造函数的作用就是把Line
转换成Point
的结合, 保存在成员points
中.
这样, 我们可以如下来绘制前面的矩形集合vectorObjects
:
1 2 3 4 5 6 7 8
| for(auto& obj: vectorObjects) { for( auto& line: *obj) { LineToPointAdapter lpo{line}; DrawPoints(dc, lpo.begin(), lpo.end()); } }
|
6.3 Adapter Trmporaries
上面的实现的问题是, 每次画图时都要重新计算一遍点.
我们可以在应用启动时先把点计算出来, 以后用时就不需要再计算了. 例如:
1 2 3 4 5 6 7 8 9 10 11 12
| vector<Point> points; for(auto& o: vectorObjects ) { for(auto& l: *o) { LineToPointAdapter lpo{l}; for(auto& p: lpo) { points.push_back(p); } } }
|
在绘制图形的时候就只需要简单地调用DrawPoints
就可以了:
1
| DrawPoints(dc, points.begin(), points.end());
|
但是, 一旦vectorObjects
发生了变化, 之前计算的cache就无效了.
为此, 我们需要唯一性地标识每个点.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| struct Point { int x,y; friend std::size_t hash_value(const Point& obj) { std::size_t seed = 0x725C686F; boost::hash_combine(seed, obj.x); boost::hash_combine(seed, obj.y); return seed; } }
struct Line { Point start, end; friend std::size_t hash_value(const Line& obj) { std::size_t seed = 0x719E6B16; boost::hash_combine(seed, obj.start); boost::hash_combine(seed, obj.end); return seed; } }
|
接下来, 我们创建一个新的LineToPointCachigAdapter
, 它会缓存点, 并且只会在必要时才重新计算.
首先, 有一个cache:
1
| static map<size_t, Points> cache;
|
然后提供对生成的点的迭代功能:
1 2 3 4 5 6 7
| virtual Points::iterator begin() { return cache[linie_hash].begin(); } virtual Points::iterator end() { return cache[line_hash].end(); }
|
类的构造函数:
1 2 3 4 5 6 7 8 9 10 11 12
| LineToPointsCachingAdapter(Line& line) { static boost::hash<Line> hash; line_hash = hash(line); if( cache.find(line_hash) != cache.end()){ return; }
Points points; cache[line_hash] = points; }
|
6.4 总结
这里比较有意思的是最后的hash值的使用, 在C++中的这种使用我倒是以前没有注意过.