创建型模式:对象的诞生艺术

创建型模式:对象的诞生艺术
写代码绕不开new。但当系统复杂到一定程度你会发现到处散落的new是个大麻烦改一个类的构造方式满世界找new的地方改想根据条件创建不同对象写出一堆if-else对象创建过程太复杂几十行初始化代码到处复制创建型模式就是来治这些病的。一共 5 种下面逐个拆解。创建型模式关心的核心问题只有一个对象怎么来的把怎么创建对象这件事封装起来让使用者不需要知道细节拿来就用。一、单例模式Singleton一句话一个类在整个系统里只有一个实例谁来要都给同一个。生活中的例子公司打印机整个办公室共用一台打印机不是每人发一台。你提交打印任务和同事提交打印任务都是发到同一台打印机。Windows 任务管理器不管你按多少次 CtrlShiftEsc打开的都是同一个窗口不会弹出来两个。为什么需要它有些东西天生只该有一份配置管理器配置只需要读一次全局共享数据库连接池连接是昂贵资源不能每次new一个日志器所有模块往同一个地方写日志应用的全局缓存数据只有一份避免不同缓存实例数据不一致如果不做限制每个角落都new一份轻则浪费内存重则数据不一致。比如两个配置管理器实例一个读了新配置另一个还是老的——系统行为就乱套了。示意图代码实现java实现双重检查锁Python 实现Python 线程安全版本注意事项问题说明解法多线程安全两个线程同时判断 instance 为空创建出两个加锁 / 双重检查 / 枚举反射攻击Java 反射可以强行调用私有构造器枚举实现可防或在构造器里加判断序列化问题反序列化会绕过构造器创建新对象加readResolve()方法测试困难全局状态难以 mock配合依赖注入框架使用二、工厂方法模式Factory Method一句话定义一个创建对象的接口让子类决定实例化哪个类。生活中的例子披萨连锁店总部定义了做披萨的标准流程揉面、放料、烤制但北京店做的是宫保鸡丁披萨纽约店做的是芝士披萨。做什么口味这个决定权交给各分店。物流公司客户只说帮我发货但走陆运还是空运还是海运由物流公司根据情况决定用哪种运输工具。手机工厂你下单买一部手机富士康帮你组装。同样的产线装不同的零件就出不同型号。为什么需要它假设你在做一个文档导出功能java// 这样写每加一种格式就要改这段代码 if (type.equals(pdf)) { return new PdfExporter(); } else if (type.equals(excel)) { return new ExcelExporter(); } else if (type.equals(word)) { return new WordExporter(); } // 后来又加了 csv、html、markdown... 这段代码改到崩溃每次新增格式都要回来改这段代码改一次就有一次出 bug 的风险。这违反了开闭原则对扩展开放对修改关闭。结构图工厂方法里有两条独立的继承/实现链这两条链是平行存在的一条管谁来造一条管造出来的东西长什么样。工厂ExportFactory是抽象类子类继承它产品Exporter是接口实现类实现它为什么这样设计ExportFactory是抽象类而不是接口因为它里面有公共代码可以复用java// 这段逻辑所有工厂都一样写在抽象类里复用 public void export(Data data, OutputStream out) { Exporter exporter createExporter(); // 调子类实现的方法 byte[] result exporter.render(data); out.write(result)子类只需要实现createExporter()这一个方法就够了其余逻辑继承父类。Exporter是接口因为产品只需要约定你必须有render()方法没有任何公共代码可以共享所以用接口更合适。代码实现java实现python实现新增格式写一个新的 Factory Exporter 就行原有代码一行不动。三、抽象工厂模式Abstract Factory一句话提供一个接口创建一系列相关的对象而不指定具体类。生活中的例子宜家家具套装你选了北欧风格那椅子、桌子、沙发、柜子全是北欧风的。你选了中式风格全套都是中式。不会出现北欧椅子配中式桌子的混搭。手机生态买了苹果手机充电器是 Lightning/USB-C、耳机是 AirPods、手表是 Apple Watch——一整套生态。买安卓则是另一套。游戏皮肤选了暗黑主题角色、武器、坐骑、特效全部是暗黑风格的配套。和工厂方法的区别工厂方法一个工厂造一种产品抽象工厂一个工厂造一族产品多种产品配套工厂方法抽象工厂一个工厂造几种产品一种一族多种解决什么问题让子类决定造哪种产品保证一整套产品风格一致例子PdfFactory 只造 PdfExporterMacFactory 造 Mac 全套 UI抽象的是什么一个创建方法一组创建方法创建方法createExporter() — 只有一个createButton() createInput() createDialog() — 多个抽象工厂的抽象更准确的理解是把创建一整族相关产品这件事抽象成了一个接口强调的是族而不只是单个产品。结构图代码实现java实现python实现四、建造者模式Builder一句话分步骤构建一个复杂对象同样的构建过程可以创建不同的表示。生活中的例子点汉堡套餐你可以自选面包类型、肉饼种类、是否加芝士、是否加生菜、要什么酱料、配什么饮料。一步步选最后组装成一个完整的套餐。装修房子地板选什么材质、墙面刷什么颜色、厨房用什么台面、卫生间装什么花洒……一项项确定最后交付一个完整的房子。写简历先填基本信息再填教育经历再填工作经验再填技能……不是一次性传 20 个参数而是分步骤填充。为什么需要它当一个对象有十几个参数构造器会变成噩梦java// 看到这种代码你能分清哪个参数是什么吗 new House(4, 2, true, false, true, 木质, null, 3, true, 现代);这段代码有三个真实的痛点看不懂7 个参数没有名字只有顺序读代码要来回对照类定义传错了不报错true 和 false 顺序写反了编译器不会提示运行时悄悄出 bug可选参数很麻烦有些房子没泳池你得要么传 false要么写一堆重载构造器参数一多顺序一乱就出 bug。有些参数是可选的有些有默认值用构造器重载的话要写几十个重载方法。Builder 怎么解决问题的java