小区里面加了好几个群,找到了团购的渠道。晚上出去分东西了

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); // 注意, line_hash是类的一个字段
if( cache.find(line_hash) != cache.end()){
return; // line已经在cache里面了
}

Points points;
//...
cache[line_hash] = points;
}

6.4 总结

这里比较有意思的是最后的hash值的使用, 在C++中的这种使用我倒是以前没有注意过.