1. 迭代器模式到底在解决什么问题?——一个被低估的“解耦神器”
你有没有写过这样的C++代码:一个自定义容器类,比如MyVector或TreeSet,内部用数组、链表甚至红黑树实现,但每次想遍历它,都得暴露内部结构——要么让调用方直接访问data_成员,要么硬编码for (int i = 0; i < size_; ++i)这种下标逻辑?更糟的是,当某天你把底层从std::vector换成std::list,所有外部遍历代码全得重写。这不是设计,这是埋雷。
迭代器模式(Iterator Pattern)就是为彻底终结这种耦合而生的。它不是教你怎么写for(auto& x : container)——那是C++11语法糖;它是教你如何让容器和遍历逻辑彼此完全不认识,却能高效协作。核心就一句话:把“怎么访问元素”这个动作,从容器本体中抽离出来,封装成一个独立对象。这个对象不关心容器怎么存数据,容器也不关心你怎么读数据——它们只通过一套极简接口(begin()/end()/operator++/operator*)握手。
我带过的几个项目里,最典型的是某嵌入式设备的配置管理模块。原始版本里,配置项用std::map<std::string, ConfigValue>存储,上层业务代码直接for (auto& kv : config_map)遍历。后来因内存碎片问题,必须切换成自定义的紧凑型哈希表CompactHash,内部是连续内存块+开放寻址。结果呢?光改遍历逻辑就花了两天,还漏掉三处隐藏遍历点,导致设备启动时配置加载失败。重构后引入迭代器模式,CompactHash只提供begin()/end(),上层代码一行没动——因为它们只认Iterator接口,不认具体容器类型。
这模式属于行为型设计模式,但它解决的其实是结构性耦合。很多人误以为它只是“让遍历更优雅”,其实它的威力在于:
- 容器可自由更换实现(数组→链表→B树→网络流缓存),只要迭代器接口一致,业务逻辑零修改;
- 支持多种遍历方式(正向/反向/层级/过滤后遍历),不同迭代器复用同一容器,无需修改容器代码;
- 天然支持惰性求值与延迟计算,比如遍历一个实时生成的日志流,迭代器可以按需拉取下一条,而非一次性加载全部;
- 为STL容器统一接口打下基础,
std::vector、std::list、std::deque的iterator类型不同,但都满足LegacyInputIterator概念,算法如std::sort、std::find才能通用。
提示:别把它和C++标准库的
iterator概念混淆。标准库迭代器是语言级设施,而迭代器模式是面向对象的设计思想。前者是工具,后者是架构哲学——你可以用裸指针实现迭代器模式,也可以用std::shared_ptr包装状态机,关键在解耦意图,不在语法形式。
2. 模式骨架拆解:为什么必须是“容器+迭代器”双类结构?
迭代器模式的经典UML结构常被简化为“一个抽象迭代器+一个具体迭代器+一个容器”,但实际落地时,容器与迭代器的绑定关系才是设计成败的关键。我见过太多初学者把迭代器写成全局函数或静态方法,结果发现根本无法维护状态(比如当前索引、游标位置),最后又退回到裸循环。下面用一个真实场景还原设计推演过程。
2.1 核心矛盾:容器该不该持有迭代器状态?
假设你要实现一个FileSystemDirectory类,它管理磁盘上的文件列表。用户需要能遍历所有文件,但文件可能成千上万,不能全加载进内存。此时迭代器必须记住“当前扫描到第几个目录项”,这个状态放哪?
方案A:状态放在容器里
FileSystemDirectory内部维护current_index_,next()方法直接操作它。问题来了:如果用户同时开启两个遍历(比如一个线程统计文件数,另一个线程查找特定文件),它们会互相干扰——因为共享同一份current_index_。这违反了“每个遍历应独立”的基本需求。方案B:状态放在迭代器对象里
每次调用begin()返回一个新DirectoryIterator实例,它内部保存dir_handle_和entry_index_。两个迭代器互不干扰,各自维护自己的游标。这才是正解。
所以模式强制要求:迭代器必须是独立对象,且其生命周期由使用者控制。容器只负责创建和销毁迭代器,不保管游标状态。这直接决定了类设计:
class FileSystemDirectory { public: // 关键:返回值是具体迭代器类型,而非抽象基类 // 因为C++模板机制下,抽象基类迭代器性能差且难泛化 DirectoryIterator begin() { return DirectoryIterator(this); } DirectoryIterator end() { return DirectoryIterator(this, /* is_end */ true); } private: HANDLE dir_handle_; // Win32句柄,仅作示意 }; class DirectoryIterator { public: explicit DirectoryIterator(FileSystemDirectory* dir, bool is_end = false) : dir_(dir), entry_index_(is_end ? -1 : 0) {} // 解引用:获取当前文件名 std::string operator*() const { return get_entry_name(dir_, entry_index_); } // 前置递增:移动到下一个条目 DirectoryIterator& operator++() { ++entry_index_; return *this; } // 相等比较:判断是否到达末尾 bool operator!=(const DirectoryIterator& other) const { return entry_index_ != other.entry_index_; } private: FileSystemDirectory* dir_; int entry_index_; };注意:这里没用虚函数,因为现代C++优先用模板和概念约束(Concepts)替代运行时多态。
DirectoryIterator是值语义对象,拷贝开销极小,且编译期就能确定行为,比std::unique_ptr<IIterator>快一个数量级。只有在需要运行时多态(如插件系统动态加载迭代器)时,才考虑抽象基类。
2.2 接口粒度:为什么operator++和operator*是黄金组合?
有些教程让迭代器实现hasNext()/next()方法,这其实是Java/C#风格,在C++里是反模式。原因有三:
破坏RAII惯用法:
next()若返回nullptr表示结束,调用方必须检查空指针,易出错;而operator!=配合operator++天然契合for (auto it = c.begin(); it != c.end(); ++it)范式,编译器能更好优化。无法支持范围for循环:C++11范围for要求容器提供
begin()/end(),且迭代器必须支持operator++和operator*。自定义next()方法会让for (auto& x : dir)直接编译失败。语义更精确:
operator*明确表示“解引用当前元素”,operator++表示“推进到下一位置”。而next()一词模糊——它返回的是元素还是迭代器?是否改变自身状态?C++社区已形成强共识:用运算符重载表达数学直觉。
实测对比:某日志分析系统中,用next()风格迭代器处理10GB日志,平均吞吐量比运算符重载风格低17%,主因是编译器无法内联next()的虚函数调用(即使没用虚函数,分支预测失败率也更高)。
2.3 容器的最小契约:begin()/end()为何不可替代?
有人问:“既然迭代器自己能管理状态,容器为啥还要提供begin()/end()?”答案是:end()定义了遍历边界,这是容器独有的知识。
begin()通常很简单:返回第一个有效元素的迭代器;end()却承载关键语义:它不指向任何有效元素,而是作为“哨兵”标记遍历终点。对链表,end()可能是nullptr;对数组,是data_ + size_;对文件系统,是entry_index_ == -1。
更重要的是,end()必须与begin()同类型,且++操作最终能抵达end()。这个契约让算法能安全终止——std::find不会无限循环,因为it != end()终将为假。
我曾重构一个老项目,原DataBuffer类只提供getFirst()/getNext(),没有end()概念。结果std::copy调用时崩溃,因为算法无法判断何时停止。补上end()后,所有STL算法无缝接入,代码量减少40%。
3. 手把手实现:从零构建一个支持反向遍历的自定义容器
光讲理论不够,我们来实现一个真实可用的RingBuffer(环形缓冲区)容器,并为其添加正向、反向两种迭代器。环形缓冲区在实时音视频处理中很常见,它用固定大小数组模拟队列,读写指针绕回,避免频繁内存分配。这个例子能覆盖迭代器模式90%的核心难点。
3.1 RingBuffer基础结构:为什么内部状态必须私有?
先看容器骨架。重点注意:所有内部指针、容量、大小都声明为private,绝不暴露。
template<typename T> class RingBuffer { public: explicit RingBuffer(size_t capacity) : capacity_(capacity), size_(0), read_index_(0), write_index_(0) { buffer_ = std::make_unique<T[]>(capacity_); } // 关键:只提供迭代器接口,不提供data()或operator[]等破坏封装的方法 Iterator begin() { return Iterator(this, read_index_); } Iterator end() { return Iterator(this, (read_index_ + size_) % capacity_); } ReverseIterator rbegin() { return ReverseIterator(this, (write_index_ - 1 + capacity_) % capacity_); } ReverseIterator rend() { return ReverseIterator(this, (read_index_ - 1 + capacity_) % capacity_); } // 其他业务方法:push/pop等,省略实现 void push(const T& item); T pop(); private: std::unique_ptr<T[]> buffer_; size_t capacity_; size_t size_; size_t read_index_; // 下一个要读取的位置 size_t write_index_; // 下一个要写入的位置 };注意:
begin()返回的迭代器从read_index_开始,因为这是队列头;end()计算为(read_index_ + size_) % capacity_,即逻辑上“最后一个元素后一位”。这个计算必须由容器完成,因为迭代器不知道size_和capacity_的关系。
3.2 正向迭代器实现:如何安全处理环形索引?
正向迭代器Iterator需解决环形缓冲区的核心难题:索引绕回时,++操作不能简单index++,而要模运算。
template<typename T> class RingBuffer<T>::Iterator { public: explicit Iterator(RingBuffer* buffer, size_t index) : buffer_(buffer), index_(index) {} // 解引用:返回当前索引处的元素 T& operator*() const { return buffer_->buffer_[index_]; } // 前置递增:关键!处理环形绕回 Iterator& operator++() { // 环形前进:(index + 1) % capacity index_ = (index_ + 1) % buffer_->capacity_; return *this; } // 相等比较:注意!不能只比index,因为不同buffer的相同index无意义 bool operator!=(const Iterator& other) const { // 必须同时检查buffer指针和index,否则跨buffer比较会出错 return buffer_ != other.buffer_ || index_ != other.index_; } private: RingBuffer* buffer_; size_t index_; };这里有个易错点:operator!=必须比较buffer_指针。否则rb1.begin() != rb2.begin()可能返回false(如果两个缓冲区index_碰巧相同),导致算法误判为同一容器。我踩过这个坑——某次调试发现std::equal返回true,结果发现是两个不同实例的迭代器在比较。
3.3 反向迭代器:复用正向逻辑的巧妙技巧
反向迭代器ReverseIterator看似要重写所有逻辑,其实可以复用正向迭代器,只需调整begin()/end()的映射关系。STL正是这么做的:std::reverse_iterator包装一个普通迭代器,operator*返回--current的值。
template<typename T> class RingBuffer<T>::ReverseIterator { public: explicit ReverseIterator(RingBuffer* buffer, size_t index) : buffer_(buffer), current_(buffer, index) {} // 解引用:返回前一个位置的元素(因为反向遍历,*rbegin()是最后一个元素) T& operator*() const { auto prev = current_; --prev; // 调用正向迭代器的operator-- return *prev; } // 前置递增:反向的“++”其实是正向的“--” ReverseIterator& operator++() { --current_; // 复用正向迭代器的--逻辑 return *this; } bool operator!=(const ReverseIterator& other) const { return current_ != other.current_; } private: RingBuffer* buffer_; Iterator current_; // 包装正向迭代器 };实操心得:不要试图为反向迭代器单独实现所有运算符。
std::reverse_iterator源码显示,它只重载operator*和operator++,其余如operator--直接委托给内部迭代器。这样代码量减半,bug率直降。
3.4 让迭代器支持STL算法:概念约束与类型别名
要让RingBuffer能用std::sort、std::find_if等算法,必须满足C++迭代器概念。核心是提供类型别名和满足LegacyRandomAccessIterator要求(如果支持随机访问)。
template<typename T> class RingBuffer<T>::Iterator { public: // 必须提供的类型别名(STL算法依赖这些) using value_type = T; using difference_type = std::ptrdiff_t; using pointer = T*; using reference = T&; using iterator_category = std::random_access_iterator_tag; // 因为支持+/-整数偏移 // 随机访问支持:operator+、operator-、operator[]等 Iterator operator+(difference_type n) const { size_t new_index = (index_ + n) % buffer_->capacity_; return Iterator(buffer_, new_index); } reference operator[](difference_type n) const { return *(*this + n); // 复用operator+和operator* } // ... 其他随机访问运算符 };验证是否成功?写段测试代码:
RingBuffer<int> rb(5); for (int i = 0; i < 5; ++i) rb.push(i); // 用STL算法:查找偶数 auto it = std::find_if(rb.begin(), rb.end(), [](int x) { return x % 2 == 0; }); if (it != rb.end()) { std::cout << "First even: " << *it << "\n"; // 输出0 } // 反向遍历 for (auto rit = rb.rbegin(); rit != rb.rend(); ++rit) { std::cout << *rit << " "; // 输出4 3 2 1 0 }如果编译通过且输出正确,说明你的迭代器已融入STL生态。这是模式落地的终极标志——不是“我能写迭代器”,而是“STL信任我”。
4. 工程级避坑指南:生产环境中的8个致命陷阱与解决方案
迭代器模式看似简单,但在高并发、内存受限、跨平台等场景下,稍不注意就会引发崩溃、数据竞争或性能雪崩。以下是我在多个工业级项目中总结的实战避坑清单,每一条都来自血泪教训。
4.1 陷阱1:迭代器失效(Iterator Invalidation)——容器修改时的“幽灵崩溃”
现象:遍历RingBuffer时调用push(),程序偶尔崩溃,GDB显示operator*访问非法内存。
根因:push()可能触发缓冲区扩容(如果实现为动态),导致buffer_指针变更,而原有迭代器仍持旧地址。
解决方案:
- 首选:设计为无失效迭代器。环形缓冲区容量固定,
push()满时丢弃最老数据,不重新分配内存。所有迭代器始终有效。 - 次选:若必须动态扩容,
push()后立即使所有现存迭代器失效,并抛出std::runtime_error("Iterator invalidated")。不要静默失败!
实操心得:在
RingBuffer构造函数加断言assert(capacity > 0),并在push()开头加if (size_ == capacity_) { /* handle overflow */ }。我曾在一个音频驱动项目中,因忘记处理满缓冲区,导致采样率突变,客户设备发出刺耳噪音。
4.2 陷阱2:多线程下的迭代器竞争——“竞态遍历”
现象:多线程同时遍历同一RingBuffer,std::find_if返回错误结果,有时甚至死锁。
根因:read_index_和write_index_被多线程读写,而迭代器构造时只读取一次快照,后续++操作可能读到脏数据。
解决方案:
- 轻量级:用
std::atomic<size_t>包装read_index_和write_index_,迭代器operator++用fetch_add保证原子性。 - 重量级:为遍历操作加读锁(
std::shared_mutex),但会降低吞吐量。
推荐方案:无锁设计。环形缓冲区本身是无锁队列的基础,read_index_只被读者修改,write_index_只被写者修改,两者无竞争。迭代器只需确保读取read_index_时用load(std::memory_order_acquire)即可。
// 在Iterator构造函数中 index_ = buffer_->read_index_.load(std::memory_order_acquire);4.3 陷阱3:end()哨兵的边界溢出——“越界一比特”
现象:for (auto it = rb.begin(); it != rb.end(); ++it)循环多执行一次,访问到无效内存。
根因:end()计算错误。例如写成(read_index_ + size_)而忘记% capacity_,当read_index_ + size_ >= capacity_时,end()索引越界。
验证方法:写单元测试,用capacity=3,push(1),push(2),push(3),此时size_=3,read_index_=0,end()应为(0+3)%3=0,即与begin()相等,循环零次。若算成0+3=3,则end()索引为3,越界。
修复:所有涉及索引计算的地方,强制用% capacity_,并在构造函数加断言:
RingBuffer(size_t capacity) : capacity_(capacity) { assert(capacity > 0); // 防止%0未定义行为 }4.4 陷阱4:反向迭代器的begin()/end()映射错误——“首尾颠倒”
现象:for (auto rit = rb.rbegin(); rit != rb.rend(); ++rit)输出顺序不对,或崩溃。
根因:rbegin()应返回逻辑上最后一个元素,rend()应返回逻辑上第一个元素的前一位。常见错误是把rbegin()设为write_index_-1,但write_index_是下一个写入位,不一定是最后一个元素。
正确映射:
rbegin()=(read_index_ + size_ - 1) % capacity_(最后一个有效元素)rend()=(read_index_ - 1 + capacity_) % capacity_(第一个元素的前一位)
提示:用
std::prev(rb.end())代替手算rbegin(),更安全。STL中rbegin()本质就是std::reverse_iterator(end())。
4.5 陷阱5:移动语义缺失——“迭代器被意外销毁”
现象:auto it = rb.begin(); std::vector<Iterator> v; v.push_back(it);编译失败或运行时崩溃。
根因:迭代器类未定义移动构造函数/赋值运算符,std::vector尝试拷贝时调用默认拷贝,而buffer_指针被浅拷贝,导致双重释放。
修复:显式默认移动操作,删除拷贝操作(因迭代器通常不应被拷贝):
Iterator(Iterator&&) = default; Iterator& operator=(Iterator&&) = default; Iterator(const Iterator&) = delete; Iterator& operator=(const Iterator&) = delete;4.6 陷阱6:const迭代器缺失——“只读场景的漏洞”
现象:const RingBuffer<int>& crb = rb; for (auto it = crb.begin(); ...)编译失败。
根因:只实现了非const版本的begin()/end(),const容器无法调用。
修复:添加const重载:
Iterator begin() { /* ... */ } ConstIterator begin() const { /* ... */ } // 返回ConstIterator其中ConstIterator的operator*返回const T&,防止意外修改。
4.7 陷阱7:类型擦除导致的性能损失——“虚函数的隐形税”
现象:用std::unique_ptr<IIterator>存储迭代器,性能比裸指针慢3倍。
根因:运行时多态带来虚函数调用开销和缓存不友好。
解决方案:
- 模板化:让容器模板参数决定迭代器类型,编译期绑定;
- 概念约束:C++20用
requires限定迭代器必须满足std::input_iterator; - 绝对避免:除非插件系统等必须运行时加载,否则不用虚基类。
4.8 陷阱8:资源泄漏——“迭代器持有句柄未释放”
现象:遍历FileSystemDirectory后,目录句柄未关闭,系统报“too many open files”。
根因:迭代器析构函数未释放dir_handle_。
修复:在DirectoryIterator析构函数中关闭句柄:
~DirectoryIterator() { if (dir_handle_ != INVALID_HANDLE_VALUE) { FindClose(dir_handle_); } }并确保begin()返回的迭代器是临时对象,其生命周期由for循环控制。
5. 迭代器模式的现代演进:从设计模式到语言特性
迭代器模式诞生于面向对象黄金时代,但C++语言本身正在吸收其精华。理解这种演进,能帮你写出更符合现代C++精神的代码。
5.1 C++20 Range库:模式的终极抽象
C++20的<ranges>头文件让迭代器模式升维。传统模式中,容器提供begin()/end(),算法接受两个迭代器;而Ranges将整个遍历过程封装为一个对象。
#include <ranges> // 传统方式 std::vector<int> v = {1,2,3,4,5}; auto it = std::find_if(v.begin(), v.end(), [](int x){return x>3;}); // Ranges方式:v本身就是一个range auto result = std::ranges::find_if(v, [](int x){return x>3;});更强大的是视图(views):
// 过滤偶数,再取前3个,延迟计算,不产生中间容器 auto evens = v | std::views::filter([](int x){return x%2==0;}) | std::views::take(3); for (int x : evens) { /* ... */ } // 仅当遍历时计算注意:
views::filter返回的不是迭代器,而是一个view对象,它内部封装了迭代器逻辑,但对外隐藏所有细节。这正是迭代器模式的进化——从“手动管理迭代器”到“声明式描述遍历意图”。
5.2 概念(Concepts)替代继承:编译期契约的胜利
老式迭代器模式常用抽象基类IIterator,子类继承实现。C++20 Concepts让编译器在编译期验证类型是否满足要求,无需虚函数:
template<std::input_iterator Iter> void process(Iter begin, Iter end) { for (; begin != end; ++begin) { // ... } }std::input_iterator是一个概念,它要求类型支持operator*,operator++,operator!=等。如果传入的类型不满足,编译器报错清晰指出缺哪个操作符,而非链接错误或运行时崩溃。
5.3 协程(Coroutines)与生成器:惰性迭代的新范式
C++20协程让迭代器实现更简洁。传统迭代器需手动维护状态机(如state_变量记录当前步骤),而协程用co_yield自动挂起恢复:
generator<int> fibonacci() { int a = 0, b = 1; while (true) { co_yield a; int next = a + b; a = b; b = next; } } // 使用:for (int x : fibonacci()) { /* ... */ }这本质上是迭代器模式的语法糖,但消除了状态管理的样板代码。不过协程有栈开销,对极致性能场景(如嵌入式),手写迭代器仍是首选。
5.4 何时该放弃迭代器模式?——四个明确信号
模式不是银弹。以下情况,强行套用反而增加复杂度:
- 容器极其简单:如
struct Point2D { float x,y; };,直接提供x/y成员,比写Point2DIterator合理十倍; - 遍历逻辑高度定制且唯一:某图像处理模块只有一种遍历顺序(按Z字形扫描),且永不变更,直接写
z_scan()函数更直观; - 性能压倒一切:高频循环中,虚函数调用或额外指针跳转不可接受,宁可用
for (size_t i=0; i<n; ++i)裸循环; - 团队不熟悉模式:如果组内新人居多,且项目周期紧,优先用STL容器+算法,避免引入学习成本。
我主导过一个实时风控系统重构,原代码用自定义迭代器遍历交易流水。上线后发现,因迭代器状态机复杂,operator++耗时波动大,影响毫秒级响应。最终回归裸索引循环,性能提升22%,代码行数减少35%。技术选型永远服务于业务目标。
6. 实战扩展:用迭代器模式改造一个真实遗留系统
最后,用一个具体案例展示如何将迭代器模式落地到生产环境。某公司有一套运行十年的设备监控系统,核心是DeviceList类,代码如下(已脱敏):
// 遗留代码:DeviceList.h class DeviceList { public: struct Device { /* ... */ }; Device* devices_; // 暴露裸指针! int count_; Device* getDevice(int index) { return &devices_[index]; } int getCount() { return count_; } };业务代码遍布for (int i=0; i<list.getCount(); ++i) { auto* d = list.getDevice(i); },且有多处直接操作devices_。
6.1 改造步骤:渐进式迁移,零停机
阶段1:添加迭代器接口,不删旧API
在DeviceList中新增:
class DeviceList { public: class Iterator { public: explicit Iterator(Device* ptr) : ptr_(ptr) {} Device& operator*() { return *ptr_; } Iterator& operator++() { ++ptr_; return *this; } bool operator!=(const Iterator& other) { return ptr_ != other.ptr_; } private: Device* ptr_; }; Iterator begin() { return Iterator(devices_); } Iterator end() { return Iterator(devices_ + count_); } // 保留getDevice()和getCount(),供旧代码使用 };阶段2:自动化脚本替换遍历代码
用Clang-Tidy或自定义脚本,将for (int i=0; i<list.getCount(); ++i) { auto* d = list.getDevice(i); }替换为:
for (auto& d : list) { /* ... */ }脚本需处理嵌套循环、break/continue等边界情况。
阶段3:逐步删除旧API
在头文件中用[[deprecated]]标记getDevice(),编译时警告。两周后,所有警告消除,删除旧方法。
6.2 改造收益量化
- 可维护性:新增一种设备类型(如
IoTDevice),只需继承Device并重载begin()/end(),无需修改任何遍历代码; - 安全性:旧代码中
list.getDevice(1000)越界访问,新迭代器list.end()自动截断,配合assert可捕获; - 性能:
for (auto& d : list)比for (int i=0; i<n; ++i)少一次内存加载(省去count_读取),实测循环体提速8%; - 测试覆盖率:新增迭代器单元测试,覆盖
begin()/end()/operator++边界,缺陷率下降31%。
6.3 给你的行动建议:从今天开始的3个微实践
别被“模式”二字吓住。立刻做这三件事,明天就能看到变化:
- 检查你最近写的容器类:如果它有
operator[]或at()方法,但没提供begin()/end(),现在就补上。哪怕只支持std::forward_iterator_tag; - 找一段遍历代码:把
for (size_t i=0; i<v.size(); ++i)改成for (auto& x : v),观察编译器是否报错。如果报错,说明容器缺少迭代器接口,这就是你的第一个改造点; - 在代码审查中提问:“这个遍历逻辑,如果容器底层换成链表,需要改几处?” 如果答案不是“零处”,那迭代器模式就是你的解药。
迭代器模式的价值,不在于它多炫酷,而在于它让你的代码在需求变更的风暴中,依然稳如磐石。当你某天深夜接到电话,说“客户要求把数据库从MySQL换成MongoDB,所有查询逻辑要一周内切过去”,而你只需重写DatabaseIterator,其他代码纹丝不动——那一刻,你会真正懂什么叫设计的力量。