Skip to content

Commit d0d0691

Browse files
feat: design patterns
1 parent 7b51038 commit d0d0691

99 files changed

Lines changed: 13703 additions & 19 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
---
2121

2222
<!-- COVERAGE_START -->
23-
![English Coverage](https://img.shields.io/badge/en_coverage-100%25-green.svg) 440/440 docs translated
23+
![English Coverage](https://img.shields.io/badge/en_coverage-95%25-green.svg) 440/461 docs translated
2424
<!-- COVERAGE_END -->
2525

2626
## 这是什么项目
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <iostream>
5+
#include <memory>
6+
#include <string>
7+
#include <unordered_map>
8+
#include <utility>
9+
#include <vector>
10+
11+
// ============================================================================
12+
// 适配器模式(Adapter)—— 把"两边接口对不上、又都不能改"的事后补救封装在一层
13+
// 翻译里。本头文件按教学顺序给出:
14+
// 1. 几何三件套 Point2D / Line / Rectangle(业务侧抽象,产出 Line)
15+
// 2. OledDriver(Adaptee:只会画 Point,改不了)
16+
// 3. LineToPointsAdapter(对象适配器:持有一份 Line 拷贝,展开成 Point)
17+
// 4. Printer / LegacyLogger / LoggerAdapter(Target + Adaptee + 对象适配器)
18+
// 5. BidirectionalAdapter(双向适配器:同时维护 Line 与 Point 两份数据)
19+
// 6. CachedLineToPointsAdapter(缓存优化:按源数据地址记忆展开结果)
20+
// 详见 documents/vol4-advanced/vol4-generics-patterns/05-adapter.md。
21+
// ============================================================================
22+
23+
// ---------------------------------------------------------------------------
24+
// 业务侧的几何抽象:Line 由两个 Point2D 组成,Rectangle 内部用四条 Line
25+
// ---------------------------------------------------------------------------
26+
struct Point2D {
27+
int x;
28+
int y;
29+
};
30+
31+
struct Line {
32+
Point2D start;
33+
Point2D end;
34+
};
35+
36+
class Rectangle {
37+
public:
38+
Rectangle(Point2D left_top, int width, int height) {
39+
Point2D rt{left_top.x + width, left_top.y};
40+
Point2D lb{left_top.x, left_top.y + height};
41+
Point2D rb{left_top.x + width, left_top.y + height};
42+
lines_ = {{left_top, rt}, {rt, rb}, {rb, lb}, {lb, left_top}};
43+
}
44+
const std::vector<Line>& lines() const { return lines_; }
45+
46+
private:
47+
std::vector<Line> lines_;
48+
};
49+
50+
// ---------------------------------------------------------------------------
51+
// Adaptee:OLED 驱动只会画点,接口要的是 [begin, end) 的 Point 区间
52+
// ---------------------------------------------------------------------------
53+
class OledDriver {
54+
public:
55+
// Adaptee 侧的接口:只认 Point,不认 Line
56+
using ConstIter = std::vector<Point2D>::const_iterator;
57+
58+
void draw_points(ConstIter begin, ConstIter end) {
59+
for (auto it = begin; it != end; ++it) {
60+
set_pixel(it->x, it->y);
61+
}
62+
}
63+
64+
private:
65+
void set_pixel(int /*x*/, int /*y*/) {
66+
++pixels_drawn_; // 这里用计数模拟"画了一个点"
67+
}
68+
69+
public:
70+
int pixels_drawn_ = 0;
71+
};
72+
73+
// ---------------------------------------------------------------------------
74+
// 对象适配器一:持有一份 Line 的拷贝,构造时展开成 Point
75+
// ---------------------------------------------------------------------------
76+
class LineToPointsAdapter {
77+
public:
78+
using ConstIter = std::vector<Point2D>::const_iterator;
79+
80+
explicit LineToPointsAdapter(const std::vector<Line>& lines) {
81+
points_.reserve(lines.size() * 2);
82+
for (const auto& l : lines) {
83+
points_.push_back(l.start);
84+
points_.push_back(l.end);
85+
}
86+
}
87+
88+
// 对外暴露的"Target 接口":一对迭代器,正好喂给 OledDriver
89+
std::pair<ConstIter, ConstIter> points() const { return {points_.begin(), points_.end()}; }
90+
91+
private:
92+
std::vector<Point2D> points_;
93+
};
94+
95+
// ---------------------------------------------------------------------------
96+
// 经典透明性示例:Target(Printer)期望 print(string),
97+
// Adaptee(LegacyLogger)只会 write_line(const char*),签名对不上
98+
// ---------------------------------------------------------------------------
99+
class Printer {
100+
public:
101+
virtual ~Printer() = default;
102+
virtual void print(const std::string& msg) = 0;
103+
};
104+
105+
// Adaptee:旧类,签名不兼容,而且改不了
106+
class LegacyLogger {
107+
public:
108+
void write_line(const char* content) { std::cout << "[legacy] " << content << "\n"; }
109+
};
110+
111+
// 对象适配器:实现 Target,内部持有 Adaptee
112+
class LoggerAdapter : public Printer {
113+
public:
114+
explicit LoggerAdapter(std::unique_ptr<LegacyLogger> adaptee) : adaptee_(std::move(adaptee)) {}
115+
116+
void print(const std::string& msg) override {
117+
adaptee_->write_line(msg.c_str()); // 翻译:std::string -> const char*
118+
}
119+
120+
private:
121+
std::unique_ptr<LegacyLogger> adaptee_;
122+
};
123+
124+
// ---------------------------------------------------------------------------
125+
// 双向适配器:内部同时维护 lines_ 与 points_,两个方向都能导出
126+
// ---------------------------------------------------------------------------
127+
class BidirectionalAdapter {
128+
public:
129+
// 方向一:从 Line 进来,顺带展开成 Point
130+
explicit BidirectionalAdapter(std::vector<Line> lines) : lines_(std::move(lines)) {
131+
points_.reserve(lines_.size() * 2);
132+
for (const auto& l : lines_) {
133+
points_.push_back(l.start);
134+
points_.push_back(l.end);
135+
}
136+
}
137+
138+
// 对外两个方向都能取:要 Line 给 Line,要 Point 给 Point
139+
const std::vector<Line>& lines() const { return lines_; }
140+
const std::vector<Point2D>& points() const { return points_; }
141+
142+
private:
143+
std::vector<Line> lines_;
144+
std::vector<Point2D> points_;
145+
};
146+
147+
// ---------------------------------------------------------------------------
148+
// 缓存优化:按源数据地址当 key,第一次展开后记住结果,后续命中即跳过展开
149+
// 注意:用裸指针当 key 要求源数据生命周期盖过缓存,内容变更不会被感知
150+
// ---------------------------------------------------------------------------
151+
class CachedLineToPointsAdapter {
152+
public:
153+
explicit CachedLineToPointsAdapter(std::vector<Line>* key) : key_(key) {}
154+
155+
const std::vector<Point2D>& get_points() {
156+
auto found = cache_.find(key_);
157+
if (found != cache_.end()) {
158+
return found->second; // 命中缓存,跳过展开
159+
}
160+
// 未命中:第一次展开,并存入缓存
161+
++expand_calls_;
162+
std::vector<Point2D> pts;
163+
pts.reserve(key_->size() * 2);
164+
for (const auto& l : *key_) {
165+
pts.push_back(l.start);
166+
pts.push_back(l.end);
167+
}
168+
return cache_[key_] = std::move(pts);
169+
}
170+
171+
static std::size_t expand_calls_; // 统计真实展开次数(演示用)
172+
173+
private:
174+
std::vector<Line>* key_;
175+
static inline std::unordered_map<std::vector<Line>*, std::vector<Point2D>> cache_;
176+
};
177+
178+
inline std::size_t CachedLineToPointsAdapter::expand_calls_ = 0;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include "Adapter.h"
2+
3+
#include <iostream>
4+
#include <memory>
5+
#include <utility>
6+
7+
// 业务代码:只依赖 Target 抽象,完全不知道 LegacyLogger 存在
8+
static void greet(Printer& p) {
9+
p.print("hello from adapter");
10+
}
11+
12+
static void demo_object_adapter() {
13+
std::cout << "== 对象适配器一:Line -> Point ============================\n";
14+
Rectangle rect({0, 0}, 10, 5);
15+
LineToPointsAdapter adapter(rect.lines());
16+
17+
OledDriver driver;
18+
auto [begin, end] = adapter.points();
19+
driver.draw_points(begin, end);
20+
21+
std::cout << "pixels_drawn = " << driver.pixels_drawn_ << " (expect 8)\n\n";
22+
}
23+
24+
static void demo_logger_adapter() {
25+
std::cout << "== 对象适配器二:Printer <-> LegacyLogger =================\n";
26+
auto logger = std::make_unique<LoggerAdapter>(std::make_unique<LegacyLogger>());
27+
greet(*logger);
28+
std::cout << "\n";
29+
}
30+
31+
static void demo_bidirectional_adapter() {
32+
std::cout << "== 双向适配器:Line <-> Point ============================\n";
33+
Rectangle rect({0, 0}, 10, 5);
34+
BidirectionalAdapter bidi(rect.lines());
35+
36+
std::cout << "bidi points = " << bidi.points().size() << " (expect 8)\n";
37+
std::cout << "bidi lines = " << bidi.lines().size() << " (expect 4)\n\n";
38+
}
39+
40+
static void demo_cached_adapter() {
41+
std::cout << "== 缓存适配器:连画五帧,展开只发生一次 ===================\n";
42+
Rectangle rect({0, 0}, 10, 5);
43+
auto lines = const_cast<std::vector<Line>&>(rect.lines());
44+
CachedLineToPointsAdapter cached(&lines);
45+
46+
constexpr int kFrames = 5;
47+
for (int i = 0; i < kFrames; ++i) {
48+
const auto& points = cached.get_points();
49+
std::cout << "frame " << i << ": points = " << points.size() << "\n";
50+
}
51+
std::cout << "expand_calls = " << CachedLineToPointsAdapter::expand_calls_ << " (expect 1)\n\n";
52+
}
53+
54+
int main() {
55+
demo_object_adapter();
56+
demo_logger_adapter();
57+
demo_bidirectional_adapter();
58+
demo_cached_adapter();
59+
return 0;
60+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
set(CMAKE_CXX_STANDARD 23)
3+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
4+
project(Adapter VERSION 0.1.0 LANGUAGES C CXX)
5+
6+
add_executable(AdapterDemo Adapter.h AdapterMain.cpp)
7+
8+
target_compile_options(AdapterDemo PRIVATE -Wall -Wextra -Wpedantic)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#pragma once
2+
3+
// 桥接模式:把「抽象」(Abstraction) 和「实现」(Implementor) 拆成两条独立的继承链,
4+
// 让抽象里持有一个实现接口的引用,用这个引用把两条链「桥接」起来。
5+
//
6+
// 本头文件演示文章的第一部分:经典的「形状 x 后端」二维分离。
7+
// - DrawingAPI:实现接口(Implementor),只描述「画的能力」,不管形状。
8+
// - OpenGLApi / DirectXApi:具体实现(ConcreteImplementor),真正的后端。
9+
// - Shape:抽象(Abstraction),持有实现接口,自己不画,委托给后端。
10+
// - Circle:精化抽象(RefinedAbstraction),只负责自己的几何。
11+
12+
#include <iostream>
13+
#include <memory>
14+
#include <utility>
15+
16+
// Implementor(实现接口):只描述「画的能力」,不管形状
17+
struct DrawingAPI {
18+
virtual ~DrawingAPI() = default;
19+
virtual void draw_circle(double x, double y, double r) = 0;
20+
};
21+
22+
// ConcreteImplementor:真正的后端实现
23+
struct OpenGLApi : DrawingAPI {
24+
void draw_circle(double x, double y, double r) override {
25+
std::cout << "[OpenGL] 绘制圆 中心(" << x << "," << y << ") 半径 " << r << "\n";
26+
}
27+
};
28+
29+
struct DirectXApi : DrawingAPI {
30+
void draw_circle(double x, double y, double r) override {
31+
std::cout << "[DirectX] 绘制圆 中心(" << x << "," << y << ") 半径 " << r << "\n";
32+
}
33+
};
34+
35+
// Abstraction:持有实现接口,自己不画,委托给后端
36+
class Shape {
37+
public:
38+
explicit Shape(std::unique_ptr<DrawingAPI> api) : api_(std::move(api)) {}
39+
virtual ~Shape() = default;
40+
virtual void draw() = 0;
41+
42+
protected:
43+
std::unique_ptr<DrawingAPI> api_;
44+
};
45+
46+
// RefinedAbstraction:具体形状,只负责自己的几何
47+
class Circle : public Shape {
48+
public:
49+
Circle(double x, double y, double r, std::unique_ptr<DrawingAPI> api)
50+
: Shape(std::move(api)), x_(x), y_(y), r_(r) {}
51+
void draw() override { api_->draw_circle(x_, y_, r_); }
52+
53+
private:
54+
double x_, y_, r_;
55+
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// 桥接模式 + pImpl 的演示入口。
2+
// 第一部分:经典 Bridge —— 同一个 Circle,注入不同后端,行为不同。
3+
// 第二部分:pImpl —— 验证 move 掏空源对象、深拷贝独立、ABI 压成一个指针。
4+
#include "Bridge.h"
5+
#include "Widget.h"
6+
7+
#include <iostream>
8+
#include <memory>
9+
#include <utility>
10+
#include <vector>
11+
12+
// ---------- 第一部分:经典 Bridge(形状 x 后端) ----------
13+
static void demo_classic_bridge() {
14+
std::cout << "== Bridge:形状 x 后端,两条链独立扩展 ===================\n";
15+
16+
Circle a(1, 2, 3, std::make_unique<OpenGLApi>());
17+
Circle b(4, 5, 6, std::make_unique<DirectXApi>());
18+
19+
std::cout << "a 用 OpenGL 后端:\n";
20+
a.draw();
21+
std::cout << "b 用 DirectX 后端:\n";
22+
b.draw();
23+
std::cout << "\n";
24+
}
25+
26+
// ---------- 第二部分:pImpl ----------
27+
static void demo_pimpl_move_and_copy() {
28+
std::cout << "== pImpl:move 掏空源对象,深拷贝独立 =====================\n";
29+
30+
Widget a;
31+
for (int i = 0; i < 100; ++i) {
32+
a.push_back(42);
33+
}
34+
35+
Widget b = a; // 拷贝:深拷贝,独立
36+
Widget c = std::move(a); // move:源对象被掏空
37+
38+
std::cout << "a.size after move = " << a.size() << " (expect 0,被 move 走了)\n";
39+
std::cout << "b.size = " << b.size() << ", b[0] = " << b.at(0)
40+
<< " (expect 100, 42,深拷贝独立)\n";
41+
std::cout << "c.size = " << c.size() << " (expect 100,move 接管)\n";
42+
43+
// 证明深拷贝真的独立:改 b 不影响 c
44+
b.push_back(7);
45+
std::cout << "after b.push_back(7): b.size = " << b.size() << ", c.size = " << c.size()
46+
<< " (c 不受影响)\n";
47+
std::cout << "\n";
48+
}
49+
50+
// 第二部分的小演示:do_work() 委托给 Impl
51+
static void demo_pimpl_do_work() {
52+
std::cout << "== pImpl:do_work() 通过桥接委托给 Impl ===================\n";
53+
Widget w;
54+
for (int i = 0; i < 10; ++i) {
55+
w.push_back(i);
56+
}
57+
w.do_work();
58+
std::cout << "\n";
59+
}
60+
61+
// 第二部分:ABI 验证 —— pImpl 之后 Widget 压成一个指针大小
62+
static void demo_pimpl_abi() {
63+
std::cout << "== pImpl:ABI 稳定,Widget 压成一个指针 ===================\n";
64+
std::cout << "sizeof(Widget) = " << sizeof(Widget) << "\n";
65+
std::cout << "sizeof(void*) = " << sizeof(void*) << "\n";
66+
std::cout << "说明:Widget 压成一个指针大小,Impl 再怎么长,Widget 的 ABI 不变\n";
67+
std::cout << "\n";
68+
}
69+
70+
int main() {
71+
demo_classic_bridge();
72+
demo_pimpl_move_and_copy();
73+
demo_pimpl_do_work();
74+
demo_pimpl_abi();
75+
return 0;
76+
}

0 commit comments

Comments
 (0)