新谈设计模式 Chapter 04 — 建造者模式 Builder
Chapter 04 — 建造者模式 Builder灵魂速记肯德基套餐一步一步配最后托盘端出来。秒懂类比去肯德基点餐“要个汉堡” → 加上汉堡“加薯条” → 加上薯条“可乐大杯” → 加上可乐“打包带走” → 构建完毕给你一个完整套餐你不需要知道汉堡怎么做的、薯条怎么炸的——你只管一步步说要什么最后拿成品。问题引入// 灾难现场构造函数参数爆炸classHouse{public:House(intfloors,introoms,boolhasGarage,boolhasPool,boolhasGarden,boolhasFancyRoof,conststd::stringstyle,intparkingSpaces,boolhasSolarPanels,...)// 还没写完……};// 调用者的噩梦Househ(2,4,true,false,true,false,modern,2,true);// 第 5 个 true 是啥来着问题参数太多根本记不住顺序很多参数是可选的但构造函数逼你全填不同配置组合需要重载 N 个构造函数模式结构┌──────────┐ 构建步骤 ┌──────────┐ │ Director │──────────────→│ Builder │ │ (导演) │ buildStepA() │ (建造者) │ │ │ buildStepB() │ │ │ │ getResult() │ │ └──────────┘ └────┬─────┘ │ 实现 ┌──────┴──────┐ │ConcreteBuilder│ │ │ │ → Product │ └──────────────┘Director 可选很多时候客户端自己就是 Director。C 实现方式一经典 Builder带 Director#includeiostream#includememory#includestring// 产品 classHouse{public:std::stringdescription()const{returnHouse: std::to_string(floors_) 层, std::to_string(rooms_) 间房(hasGarage_?, 有车库:)(hasPool_?, 有泳池:)(hasGarden_?, 有花园:);}intfloors_1;introoms_1;boolhasGarage_false;boolhasPool_false;boolhasGarden_false;};// 建造者接口 classHouseBuilder{public:virtual~HouseBuilder()default;virtualHouseBuildersetFloors(intn)0;virtualHouseBuildersetRooms(intn)0;virtualHouseBuilderaddGarage()0;virtualHouseBuilderaddPool()0;virtualHouseBuilderaddGarden()0;virtualstd::unique_ptrHousebuild()0;};// 具体建造者 classConcreteHouseBuilder:publicHouseBuilder{public:ConcreteHouseBuilder(){reset();}voidreset(){house_std::make_uniqueHouse();}HouseBuildersetFloors(intn)override{house_-floors_n;return*this;}HouseBuildersetRooms(intn)override{house_-rooms_n;return*this;}HouseBuilderaddGarage()override{house_-hasGarage_true;return*this;}HouseBuilderaddPool()override{house_-hasPool_true;return*this;}HouseBuilderaddGarden()override{house_-hasGarden_true;return*this;}std::unique_ptrHousebuild()override{autoresultstd::move(house_);reset();// 重置准备构建下一个returnresult;}private:std::unique_ptrHousehouse_;};// Director可选封装常见配置 classDirector{public:// 经济适用房staticstd::unique_ptrHousebuildCheapHouse(HouseBuilderbuilder){returnbuilder.setFloors(1).setRooms(2).build();}// 豪宅staticstd::unique_ptrHousebuildLuxuryHouse(HouseBuilderbuilder){returnbuilder.setFloors(3).setRooms(8).addGarage().addPool().addGarden().build();}};intmain(){ConcreteHouseBuilder builder;// 用 Director 建预设方案autocheapDirector::buildCheapHouse(builder);std::coutcheap-description()\n;autoluxuryDirector::buildLuxuryHouse(builder);std::coutluxury-description()\n;// 或者客户端自己当 Director链式调用autocustombuilder.setFloors(2).setRooms(4).addGarden().build();std::coutcustom-description()\n;}输出House: 1 层, 2 间房 House: 3 层, 8 间房, 有车库, 有泳池, 有花园 House: 2 层, 4 间房, 有花园方式二现代 C 流畅接口实战中最常用在实际工程中Builder 最常见的形态不是上面那种带 Director 的经典版而是直接在类本身上做链式调用#includeiostream#includestring#includevector#includeutilityclassHttpRequest{public:HttpRequestsetUrl(std::string url){url_std::move(url);// std::move 避免多余拷贝return*this;// 返回自身引用实现链式调用}HttpRequestsetMethod(std::string method){method_std::move(method);return*this;}HttpRequestaddHeader(std::string key,std::string val){headers_.emplace_back(std::move(key),std::move(val));return*this;}HttpRequestsetBody(std::string body){body_std::move(body);return*this;}voidsend()const{std::coutmethod_ url_\n;for(constauto[k,v]:headers_){std::coutk: v\n;}if(!body_.empty())std::cout\nbody_\n;}private:std::string url_;std::string method_GET;// 默认值——不设就用 GETstd::vectorstd::pairstd::string,std::stringheaders_;std::string body_;};intmain(){// 链式调用像写自然语言HttpRequest().setUrl(https://api.example.com/users).setMethod(POST).addHeader(Content-Type,application/json).setBody(R({name: 张三})).send();}为什么参数用std::string而不是const std::string这里用的是按值传参再std::move的惯用法。如果调用者传的是左值会拷贝一次再移动如果传的是右值比如字面量POST直接构造临时std::string然后移动。两种情况下都只有一次拷贝或零拷贝性能和const std::string 内部赋值基本相当但代码更简单。这是 C11 以来推荐的sink parameter写法。什么时候用✅ 适合❌ 别用构造参数 4 个构造很简单两三个参数很多参数是可选的所有参数都是必填的想要链式调用的 API对象一旦创建就不会变不需要分步同样的构建过程创建不同表示产品类型很少且固定防混淆Builder vs Abstract FactoryBuilderAbstract Factory生产什么一个复杂对象一族相关对象构建方式分步骤可以不同顺序一步到位关注点构建过程产品家族类比一步步配套餐选一个风格全家桶拿走一句话分清Builder 关注怎么造Abstract Factory 关注造哪套。Builder vs 构造函数重载Builder构造函数重载可读性.setX().setY()一目了然参数位置靠猜可选参数天然支持需要大量重载验证可以在 build() 中统一校验分散在各个重载中实战经验链式调用返回引用return *this;是 Builder 的灵魂build() 做校验在build()里检查必填项比在构造函数里优雅不可变对象Builder 构建完后产品对象可以设计成 immutable// build() 中校验的例子std::unique_ptrHousebuild(){if(house_-rooms_1)throwstd::invalid_argument(至少要有一间房);returnstd::move(house_);}