Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
---

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

## 这是什么项目
Expand Down
178 changes: 178 additions & 0 deletions code/volumn_codes/vol4/design-patterns/Adapter/Adapter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#pragma once

#include <cstddef>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

// ============================================================================
// 适配器模式(Adapter)—— 把"两边接口对不上、又都不能改"的事后补救封装在一层
// 翻译里。本头文件按教学顺序给出:
// 1. 几何三件套 Point2D / Line / Rectangle(业务侧抽象,产出 Line)
// 2. OledDriver(Adaptee:只会画 Point,改不了)
// 3. LineToPointsAdapter(对象适配器:持有一份 Line 拷贝,展开成 Point)
// 4. Printer / LegacyLogger / LoggerAdapter(Target + Adaptee + 对象适配器)
// 5. BidirectionalAdapter(双向适配器:同时维护 Line 与 Point 两份数据)
// 6. CachedLineToPointsAdapter(缓存优化:按源数据地址记忆展开结果)
// 详见 documents/vol4-advanced/vol4-generics-patterns/05-adapter.md。
// ============================================================================

// ---------------------------------------------------------------------------
// 业务侧的几何抽象:Line 由两个 Point2D 组成,Rectangle 内部用四条 Line
// ---------------------------------------------------------------------------
struct Point2D {
int x;
int y;
};

struct Line {
Point2D start;
Point2D end;
};

class Rectangle {
public:
Rectangle(Point2D left_top, int width, int height) {
Point2D rt{left_top.x + width, left_top.y};
Point2D lb{left_top.x, left_top.y + height};
Point2D rb{left_top.x + width, left_top.y + height};
lines_ = {{left_top, rt}, {rt, rb}, {rb, lb}, {lb, left_top}};
}
const std::vector<Line>& lines() const { return lines_; }

private:
std::vector<Line> lines_;
};

// ---------------------------------------------------------------------------
// Adaptee:OLED 驱动只会画点,接口要的是 [begin, end) 的 Point 区间
// ---------------------------------------------------------------------------
class OledDriver {
public:
// Adaptee 侧的接口:只认 Point,不认 Line
using ConstIter = std::vector<Point2D>::const_iterator;

void draw_points(ConstIter begin, ConstIter end) {
for (auto it = begin; it != end; ++it) {
set_pixel(it->x, it->y);
}
}

private:
void set_pixel(int /*x*/, int /*y*/) {
++pixels_drawn_; // 这里用计数模拟"画了一个点"
}

public:
int pixels_drawn_ = 0;
};

// ---------------------------------------------------------------------------
// 对象适配器一:持有一份 Line 的拷贝,构造时展开成 Point
// ---------------------------------------------------------------------------
class LineToPointsAdapter {
public:
using ConstIter = std::vector<Point2D>::const_iterator;

explicit LineToPointsAdapter(const std::vector<Line>& lines) {
points_.reserve(lines.size() * 2);
for (const auto& l : lines) {
points_.push_back(l.start);
points_.push_back(l.end);
}
}

// 对外暴露的"Target 接口":一对迭代器,正好喂给 OledDriver
std::pair<ConstIter, ConstIter> points() const { return {points_.begin(), points_.end()}; }

private:
std::vector<Point2D> points_;
};

// ---------------------------------------------------------------------------
// 经典透明性示例:Target(Printer)期望 print(string),
// Adaptee(LegacyLogger)只会 write_line(const char*),签名对不上
// ---------------------------------------------------------------------------
class Printer {
public:
virtual ~Printer() = default;
virtual void print(const std::string& msg) = 0;
};

// Adaptee:旧类,签名不兼容,而且改不了
class LegacyLogger {
public:
void write_line(const char* content) { std::cout << "[legacy] " << content << "\n"; }
};

// 对象适配器:实现 Target,内部持有 Adaptee
class LoggerAdapter : public Printer {
public:
explicit LoggerAdapter(std::unique_ptr<LegacyLogger> adaptee) : adaptee_(std::move(adaptee)) {}

void print(const std::string& msg) override {
adaptee_->write_line(msg.c_str()); // 翻译:std::string -> const char*
}

private:
std::unique_ptr<LegacyLogger> adaptee_;
};

// ---------------------------------------------------------------------------
// 双向适配器:内部同时维护 lines_ 与 points_,两个方向都能导出
// ---------------------------------------------------------------------------
class BidirectionalAdapter {
public:
// 方向一:从 Line 进来,顺带展开成 Point
explicit BidirectionalAdapter(std::vector<Line> lines) : lines_(std::move(lines)) {
points_.reserve(lines_.size() * 2);
for (const auto& l : lines_) {
points_.push_back(l.start);
points_.push_back(l.end);
}
}

// 对外两个方向都能取:要 Line 给 Line,要 Point 给 Point
const std::vector<Line>& lines() const { return lines_; }
const std::vector<Point2D>& points() const { return points_; }

private:
std::vector<Line> lines_;
std::vector<Point2D> points_;
};

// ---------------------------------------------------------------------------
// 缓存优化:按源数据地址当 key,第一次展开后记住结果,后续命中即跳过展开
// 注意:用裸指针当 key 要求源数据生命周期盖过缓存,内容变更不会被感知
// ---------------------------------------------------------------------------
class CachedLineToPointsAdapter {
public:
explicit CachedLineToPointsAdapter(std::vector<Line>* key) : key_(key) {}

const std::vector<Point2D>& get_points() {
auto found = cache_.find(key_);
if (found != cache_.end()) {
return found->second; // 命中缓存,跳过展开
}
// 未命中:第一次展开,并存入缓存
++expand_calls_;
std::vector<Point2D> pts;
pts.reserve(key_->size() * 2);
for (const auto& l : *key_) {
pts.push_back(l.start);
pts.push_back(l.end);
}
return cache_[key_] = std::move(pts);
}

static std::size_t expand_calls_; // 统计真实展开次数(演示用)

private:
std::vector<Line>* key_;
static inline std::unordered_map<std::vector<Line>*, std::vector<Point2D>> cache_;
};

inline std::size_t CachedLineToPointsAdapter::expand_calls_ = 0;
60 changes: 60 additions & 0 deletions code/volumn_codes/vol4/design-patterns/Adapter/AdapterMain.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include "Adapter.h"

#include <iostream>
#include <memory>
#include <utility>

// 业务代码:只依赖 Target 抽象,完全不知道 LegacyLogger 存在
static void greet(Printer& p) {
p.print("hello from adapter");
}

static void demo_object_adapter() {
std::cout << "== 对象适配器一:Line -> Point ============================\n";
Rectangle rect({0, 0}, 10, 5);
LineToPointsAdapter adapter(rect.lines());

OledDriver driver;
auto [begin, end] = adapter.points();
driver.draw_points(begin, end);

std::cout << "pixels_drawn = " << driver.pixels_drawn_ << " (expect 8)\n\n";
}

static void demo_logger_adapter() {
std::cout << "== 对象适配器二:Printer <-> LegacyLogger =================\n";
auto logger = std::make_unique<LoggerAdapter>(std::make_unique<LegacyLogger>());
greet(*logger);
std::cout << "\n";
}

static void demo_bidirectional_adapter() {
std::cout << "== 双向适配器:Line <-> Point ============================\n";
Rectangle rect({0, 0}, 10, 5);
BidirectionalAdapter bidi(rect.lines());

std::cout << "bidi points = " << bidi.points().size() << " (expect 8)\n";
std::cout << "bidi lines = " << bidi.lines().size() << " (expect 4)\n\n";
}

static void demo_cached_adapter() {
std::cout << "== 缓存适配器:连画五帧,展开只发生一次 ===================\n";
Rectangle rect({0, 0}, 10, 5);
auto lines = const_cast<std::vector<Line>&>(rect.lines());
CachedLineToPointsAdapter cached(&lines);

constexpr int kFrames = 5;
for (int i = 0; i < kFrames; ++i) {
const auto& points = cached.get_points();
std::cout << "frame " << i << ": points = " << points.size() << "\n";
}
std::cout << "expand_calls = " << CachedLineToPointsAdapter::expand_calls_ << " (expect 1)\n\n";
}

int main() {
demo_object_adapter();
demo_logger_adapter();
demo_bidirectional_adapter();
demo_cached_adapter();
return 0;
}
8 changes: 8 additions & 0 deletions code/volumn_codes/vol4/design-patterns/Adapter/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project(Adapter VERSION 0.1.0 LANGUAGES C CXX)

add_executable(AdapterDemo Adapter.h AdapterMain.cpp)

target_compile_options(AdapterDemo PRIVATE -Wall -Wextra -Wpedantic)
55 changes: 55 additions & 0 deletions code/volumn_codes/vol4/design-patterns/Bridge/Bridge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once

// 桥接模式:把「抽象」(Abstraction) 和「实现」(Implementor) 拆成两条独立的继承链,
// 让抽象里持有一个实现接口的引用,用这个引用把两条链「桥接」起来。
//
// 本头文件演示文章的第一部分:经典的「形状 x 后端」二维分离。
// - DrawingAPI:实现接口(Implementor),只描述「画的能力」,不管形状。
// - OpenGLApi / DirectXApi:具体实现(ConcreteImplementor),真正的后端。
// - Shape:抽象(Abstraction),持有实现接口,自己不画,委托给后端。
// - Circle:精化抽象(RefinedAbstraction),只负责自己的几何。

#include <iostream>
#include <memory>
#include <utility>

// Implementor(实现接口):只描述「画的能力」,不管形状
struct DrawingAPI {
virtual ~DrawingAPI() = default;
virtual void draw_circle(double x, double y, double r) = 0;
};

// ConcreteImplementor:真正的后端实现
struct OpenGLApi : DrawingAPI {
void draw_circle(double x, double y, double r) override {
std::cout << "[OpenGL] 绘制圆 中心(" << x << "," << y << ") 半径 " << r << "\n";
}
};

struct DirectXApi : DrawingAPI {
void draw_circle(double x, double y, double r) override {
std::cout << "[DirectX] 绘制圆 中心(" << x << "," << y << ") 半径 " << r << "\n";
}
};

// Abstraction:持有实现接口,自己不画,委托给后端
class Shape {
public:
explicit Shape(std::unique_ptr<DrawingAPI> api) : api_(std::move(api)) {}
virtual ~Shape() = default;
virtual void draw() = 0;

protected:
std::unique_ptr<DrawingAPI> api_;
};

// RefinedAbstraction:具体形状,只负责自己的几何
class Circle : public Shape {
public:
Circle(double x, double y, double r, std::unique_ptr<DrawingAPI> api)
: Shape(std::move(api)), x_(x), y_(y), r_(r) {}
void draw() override { api_->draw_circle(x_, y_, r_); }

private:
double x_, y_, r_;
};
76 changes: 76 additions & 0 deletions code/volumn_codes/vol4/design-patterns/Bridge/BridgeMain.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 桥接模式 + pImpl 的演示入口。
// 第一部分:经典 Bridge —— 同一个 Circle,注入不同后端,行为不同。
// 第二部分:pImpl —— 验证 move 掏空源对象、深拷贝独立、ABI 压成一个指针。
#include "Bridge.h"
#include "Widget.h"

#include <iostream>
#include <memory>
#include <utility>
#include <vector>

// ---------- 第一部分:经典 Bridge(形状 x 后端) ----------
static void demo_classic_bridge() {
std::cout << "== Bridge:形状 x 后端,两条链独立扩展 ===================\n";

Circle a(1, 2, 3, std::make_unique<OpenGLApi>());
Circle b(4, 5, 6, std::make_unique<DirectXApi>());

std::cout << "a 用 OpenGL 后端:\n";
a.draw();
std::cout << "b 用 DirectX 后端:\n";
b.draw();
std::cout << "\n";
}

// ---------- 第二部分:pImpl ----------
static void demo_pimpl_move_and_copy() {
std::cout << "== pImpl:move 掏空源对象,深拷贝独立 =====================\n";

Widget a;
for (int i = 0; i < 100; ++i) {
a.push_back(42);
}

Widget b = a; // 拷贝:深拷贝,独立
Widget c = std::move(a); // move:源对象被掏空

std::cout << "a.size after move = " << a.size() << " (expect 0,被 move 走了)\n";
std::cout << "b.size = " << b.size() << ", b[0] = " << b.at(0)
<< " (expect 100, 42,深拷贝独立)\n";
std::cout << "c.size = " << c.size() << " (expect 100,move 接管)\n";

// 证明深拷贝真的独立:改 b 不影响 c
b.push_back(7);
std::cout << "after b.push_back(7): b.size = " << b.size() << ", c.size = " << c.size()
<< " (c 不受影响)\n";
std::cout << "\n";
}

// 第二部分的小演示:do_work() 委托给 Impl
static void demo_pimpl_do_work() {
std::cout << "== pImpl:do_work() 通过桥接委托给 Impl ===================\n";
Widget w;
for (int i = 0; i < 10; ++i) {
w.push_back(i);
}
w.do_work();
std::cout << "\n";
}

// 第二部分:ABI 验证 —— pImpl 之后 Widget 压成一个指针大小
static void demo_pimpl_abi() {
std::cout << "== pImpl:ABI 稳定,Widget 压成一个指针 ===================\n";
std::cout << "sizeof(Widget) = " << sizeof(Widget) << "\n";
std::cout << "sizeof(void*) = " << sizeof(void*) << "\n";
std::cout << "说明:Widget 压成一个指针大小,Impl 再怎么长,Widget 的 ABI 不变\n";
std::cout << "\n";
}

int main() {
demo_classic_bridge();
demo_pimpl_move_and_copy();
demo_pimpl_do_work();
demo_pimpl_abi();
return 0;
}
Loading
Loading