ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Design-Patterns-In-Swift 结构型设计模式实战:九大模式的 Swift 5.0 实现与源码解析

Design-Patterns-In-Swift 结构型设计模式实战:九大模式的 Swift 5.0 实现与源码解析 Design-Patterns-In-Swift 结构型设计模式实战九大模式的 Swift 5.0 实现与源码解析【免费下载链接】Design-Patterns-In-Swift Design Patterns implemented in Swift 5.0项目地址: https://gitcode.com/gh_mirrors/de/Design-Patterns-In-Swift结构型设计模式Structural Design Patterns通过识别实体之间关系的简单实现方式来简化软件设计。本文以 Design-Patterns-In-Swift 仓库 source/structural/ 目录下的完整 Swift 5.0 源码为骨架逐一剖析 Adapter、Bridge、Composite、Decorator、Facade、Flyweight 及两种 Proxy 模式的真实实现、关键原理与适用场景读者可据此掌握在 Swift 项目中运用结构型模式组织类与对象协作的完整实战方案。一、结构型设计模式是什么从章节定义说起在仓库的 source/structural/header.md 中结构型模式的章节定义如下In software engineering, structural design patterns are design patterns that ease the design by identifying a simple way to realize relationships between entities.这段定义源自维基百科 Structural pattern 条目点明了结构型模式的本质它关注的不是如何创建对象那是创建型模式的职责也不是对象之间如何通信协作那是行为型模式的职责而是如何以清晰、简单的方式组织类与对象之间的静态结构关系。具体来说结构型模式回答三类核心问题接口不兼容时如何协作—— Adapter、Bridge如何用一致的方式处理单个对象与对象集合—— Composite、Decorator如何在不动摇调用方的前提下隔离复杂性或节省资源—— Facade、Flyweight、Proxy。在 Design-Patterns-In-Swift 仓库中结构型模式对应 source/structural/ 目录下的 8 个独立 Swift 源文件Proxy 模式被细分为保护代理与虚拟代理两种变体因此共 9 个实现视角并在 Design-Patterns.playground/Pages/Structural.xcplaygroundpage/Contents.swift 中整合为可运行的 Xcode Playground 页面。二、仓库结构型模式全景8 个源文件、9 种实现视角source/structural/ ├── adapter.swift # 适配器 ├── bridge.swift # 桥接 ├── composite.swift # 组合 ├── decorator.swift # 装饰器 ├── facade.swift # 外观 ├── flyweight.swift # 享元 ├── header.md # 章节定义本文主题来源 ├── protection_proxy.swift # ☔ 保护代理 └── virtual_proxy.swift # 虚拟代理每个.swift文件都以 Swift Playground 的 Markup 注释/*:开头依次给出模式的定义、Example 代码、Usage 调用示例三段式结构。这些源文件通过 generate-playground.sh 与 imports.swift 等脚手架拼接进 Playground 页面中文版对应 source-cn/structural/ 目录与 Design-Patterns-CN.playground。下面逐个深入每个模式的源码实现。三、 Adapter 适配器让不兼容的接口重新对齐模式定义见 adapter.swift适配器模式通过用一个支持客户端所需接口的类去包装被适配者adaptee从而为两个原本不兼容的类型建立连接。源码实现protocol NewDeathStarSuperLaserAiming { var angleV: Double { get } var angleH: Double { get } } // 被适配者旧系统使用 Float 表示瞄准角度 struct OldDeathStarSuperlaserTarget { let angleHorizontal: Float let angleVertical: Float init(angleHorizontal: Float, angleVertical: Float) { self.angleHorizontal angleHorizontal self.angleVertical angleVertical } } // 适配器包装旧目标转换为新协议要求的 Double 接口 struct NewDeathStarSuperlaserTarget: NewDeathStarSuperLaserAiming { private let target: OldDeathStarSuperlaserTarget var angleV: Double { return Double(target.angleVertical) } var angleH: Double { return Double(target.angleHorizontal) } init(_ target: OldDeathStarSuperlaserTarget) { self.target target } }调用示例let target OldDeathStarSuperlaserTarget(angleHorizontal: 14.0, angleVertical: 12.0) let newFormat NewDeathStarSuperlaserTarget(target) newFormat.angleH // 14.0 newFormat.angleV // 12.0原理剖析这段代码完整展示了适配器模式的三要素Target目标接口NewDeathStarSuperLaserAiming协议定义了客户端期望的Double类型角度属性Adaptee被适配者OldDeathStarSuperlaserTarget旧代码用Float存储角度无法直接满足新接口Adapter适配器NewDeathStarSuperlaserTarget持有被适配者并通过构造器注入在计算属性中完成Float → Double的类型转换。值得注意的实现细节适配器以struct值类型实现并通过private let target保持对被适配者的封装隐藏细节、不暴露内部状态这正是包装wrapping语义的体现。适配器的核心价值是不修改旧代码——OldDeathStarSuperlaserTarget本身零改动所有兼容逻辑集中在适配层符合开闭原则。四、 Bridge 桥接把抽象与实现解耦各自独立演化模式定义见 bridge.swift桥接模式将类的抽象元素与实现细节分离从而可以在不修改抽象的前提下替换实现细节。源码实现protocol Switch { var appliance: Appliance { get set } func turnOn() } protocol Appliance { func run() } final class RemoteControl: Switch { var appliance: Appliance func turnOn() { self.appliance.run() } init(appliance: Appliance) { self.appliance appliance } } final class TV: Appliance { func run() { print(tv turned on); } } final class VacuumCleaner: Appliance { func run() { print(vacuum cleaner turned on) } }调用示例let tvRemoteControl RemoteControl(appliance: TV()) tvRemoteControl.turnOn() let fancyVacuumCleanerRemoteControl RemoteControl(appliance: VacuumCleaner()) fancyVacuumCleanerRemoteControl.turnOn()原理剖析桥接模式把抽象维度与实现维度拆成两条独立的继承/协议层次抽象层Switch协议与RemoteControl类只关心开关这一高层操作实现层Appliance协议及TV、VacuumCleaner等具体电器只关心如何运行。RemoteControl通过var appliance: Appliance用var而非let意味着运行期可动态更换电器持有实现层的引用turnOn()将调用委托给appliance.run()。新增一种电器实现维度扩展或新增一种遥控器抽象维度扩展互不影响——这正是桥接模式相比继承爆炸的优势若用继承实现每增加一种电器就要新增一种遥控器子类组合数呈乘法增长桥接后呈加法增长。五、 Composite 组合以一致的方式处理单个对象与对象树模式定义见 composite.swift组合模式用于创建相关对象的层级化、递归树状结构结构中任意元素都可以用标准方式被访问和利用。源码实现// Component统一组件接口 protocol Shape { func draw(fillColor: String) } // Leafs叶子节点 final class Square: Shape { func draw(fillColor: String) { print(Drawing a Square with color \(fillColor)) } } final class Circle: Shape { func draw(fillColor: String) { print(Drawing a circle with color \(fillColor)) } } // Composite组合节点内部持有子组件集合 final class Whiteboard: Shape { private lazy var shapes [Shape]() init(_ shapes: Shape...) { self.shapes shapes } func draw(fillColor: String) { for shape in self.shapes { shape.draw(fillColor: fillColor) } } }调用示例var whiteboard Whiteboard(Circle(), Square()) whiteboard.draw(fillColor: Red) // 输出 // Drawing a circle with color Red // Drawing a Square with color Red原理剖析组合模式的三层结构在本例中非常清晰ComponentShape协议定义了叶子与组合节点共同的draw(fillColor:)操作LeafSquare、Circle实现具体绘制CompositeWhiteboard持有[Shape]数组将draw递归转发给每个子组件。两个实现细节值得强调Whiteboard的构造器使用可变参数init(_ shapes: Shape...)允许一次性传入任意数量子组件调用端代码更简洁private lazy var shapes延迟初始化数组仅在首次使用时才分配存储。由于Whiteboard本身也遵循Shape组合节点可以被当作叶子继续嵌入更大的组合如白板里嵌套白板从而形成任意深度的递归树。对客户端而言调用单个图形与调用整个白板毫无差别——这就是单个对象与对象集合的一致性。六、 Decorator 装饰器运行时动态扩展对象能力模式定义见 decorator.swift装饰器模式通过在装饰类中包装对象在运行时扩展或改变对象的功能是继承之外修改行为的灵活替代方案。源码实现protocol CostHaving { var cost: Double { get } } protocol IngredientsHaving { var ingredients: [String] { get } } typealias BeverageDataHaving CostHaving IngredientsHaving struct SimpleCoffee: BeverageDataHaving { let cost: Double 1.0 let ingredients [Water, Coffee] } protocol BeverageHaving: BeverageDataHaving { var beverage: BeverageDataHaving { get } } struct Milk: BeverageHaving { let beverage: BeverageDataHaving var cost: Double { return beverage.cost 0.5 } var ingredients: [String] { return beverage.ingredients [Milk] } } struct WhipCoffee: BeverageHaving { let beverage: BeverageDataHaving var cost: Double { return beverage.cost 0.5 } var ingredients: [String] { return beverage.ingredients [Whip] } }调用示例var someCoffee: BeverageDataHaving SimpleCoffee() print(Cost: \(someCoffee.cost); Ingredients: \(someCoffee.ingredients)) // Cost: 1.0; Ingredients: [Water, Coffee] someCoffee Milk(beverage: someCoffee) print(Cost: \(someCoffee.cost); Ingredients: \(someCoffee.ingredients)) // Cost: 1.5; Ingredients: [Water, Coffee, Milk] someCoffee WhipCoffee(beverage: someCoffee) print(Cost: \(someCoffee.cost); Ingredients: \(someCoffee.ingredients)) // Cost: 2.0; Ingredients: [Water, Coffee, Milk, Whip]原理剖析这是装饰器模式的教科书式实现且展示了 Swift 特有的协议组合特性基础组件SimpleCoffee成本 1.0、配料为水与咖啡装饰器Milk、WhipCoffee各自持有beverage: BeverageDataHaving引用在计算属性中追加成本与配料协议组合typealias BeverageDataHaving CostHaving IngredientsHaving将两个协议合成为单一约束配合BeverageHaving协议保证了装饰器既能读取被装饰对象数据、又能暴露一致的cost/ingredients接口。注意装饰链的叠加效果SimpleCoffee → Milk → WhipCoffee后成本变为 1.0 0.5 0.5 2.0配料依次累积。装饰器是组合优于继承的最佳例证要为咖啡添加 8 种配料继承方案需要 2⁸ 个类而装饰器只需 8 个类自由排列组合。七、 Facade 外观为复杂子系统提供统一简化入口模式定义见 facade.swift外观模式用于为更复杂的子系统定义一个简化接口。源码实现final class Defaults { private let defaults: UserDefaults init(defaults: UserDefaults .standard) { self.defaults defaults } subscript(key: String) - String? { get { return defaults.string(forKey: key) } set { defaults.set(newValue, forKey: key) } } }调用示例let storage Defaults() // Store storage[Bishop] Disconnect me. Id rather be nothing // Read storage[Bishop]原理剖析这个例子用UserDefaults展示了外观模式的精髓——把子系统UserDefaults 的 key-value API包装成一个更符合业务语义的简化接口封装细节private let defaults隐藏了底层UserDefaults实例简化访问通过 Swift 下标语法subscript把存取字符串从UserDefaults.standard.string(forKey:)/.set(_:forKey:)两步调用简化为一对storage[key]读写默认参数init(defaults: UserDefaults .standard)让调用方无需关心底层对象开箱即用。外观模式的核心收益是降低客户端与子系统的耦合当底层存储从UserDefaults迁移到数据库或网络时只需修改Defaults内部实现所有调用方代码零改动。需要注意 Facade 不阻止客户端直接使用子系统它只是提供一个更顺手的门面。八、 Flyweight 享元共享对象最小化内存占用模式定义见 flyweight.swift享元模式通过尽可能与其他相似对象共享状态来最小化内存占用或计算开销。源码实现// Instances of SpecialityCoffee will be the Flyweights struct SpecialityCoffee { let origin: String } protocol CoffeeSearching { func search(origin: String) - SpecialityCoffee? } // Menu acts as a factory and cache for SpecialityCoffee flyweight objects final class Menu: CoffeeSearching { private var coffeeAvailable: [String: SpecialityCoffee] [:] func search(origin: String) - SpecialityCoffee? { if coffeeAvailable.index(forKey: origin) nil { coffeeAvailable[origin] SpecialityCoffee(origin: origin) } return coffeeAvailable[origin] } } final class CoffeeShop { private var orders: [Int: SpecialityCoffee] [:] private let menu: CoffeeSearching init(menu: CoffeeSearching) { self.menu menu } func takeOrder(origin: String, table: Int) { orders[table] menu.search(origin: origin) } func serve() { for (table, origin) in orders { print(Serving \(origin) to table \(table)) } } }调用示例let coffeeShop CoffeeShop(menu: Menu()) coffeeShop.takeOrder(origin: Yirgacheffe, Ethiopia, table: 1) coffeeShop.takeOrder(origin: Buziraguhindwa, Burundi, table: 3) coffeeShop.serve()原理剖析享元模式解决的是大量相似对象重复创建的资源浪费问题本例用咖啡店场景展示了完整的享元架构Flyweight享元对象SpecialityCoffee只包含不可变的内在状态origin产地享元工厂兼缓存Menu以[String: SpecialityCoffee]字典缓存已创建的咖啡对象search(origin:)采用先查缓存、未命中才创建的策略类似 memoization客户端CoffeeShop下单时通过menu.search(origin:)获取享元不同桌号对同一产地的咖啡共享同一个SpecialityCoffee实例。关键洞察当 100 桌客人都点Yirgacheffe咖啡时内存中只存在一个SpecialityCoffee实例而非 100 个。CoffeeShop中orders: [Int: SpecialityCoffee]的字典值全是共享引用。代码注释也明确标出了这一设计Menu acts as a factory and cache for SpecialityCoffee flyweight objects。九、☔ Protection Proxy 保护代理为访问控制加一道闸门模式定义见 protection_proxy.swift代理模式提供一个引用底层对象的替身或占位对象保护代理的作用是限制访问。源码实现protocol DoorOpening { func open(doors: String) - String } final class HAL9000: DoorOpening { func open(doors: String) - String { return (HAL9000: Affirmative, Dave. I read you. Opened \(doors).) } } final class CurrentComputer: DoorOpening { private var computer: HAL9000! func authenticate(password: String) - Bool { guard password pass else { return false } computer HAL9000() return true } func open(doors: String) - String { guard computer ! nil else { return Access Denied. Im afraid I cant do that. } return computer.open(doors: doors) } }调用示例let computer CurrentComputer() let podBay Pod Bay Doors computer.open(doors: podBay) // Access Denied. Im afraid I cant do that. computer.authenticate(password: pass) computer.open(doors: podBay) // HAL9000: Affirmative, Dave. I read you. Opened Pod Bay Doors.原理剖析保护代理的职责是在委托真实对象之前执行权限校验。本例借用了《2001 太空漫游》中 HAL9000 的经典桥段真实主题HAL9000实际执行开门操作代理CurrentComputer与真实主题实现同一协议DoorOpening客户端无感知但内部持有可选的computer: HAL9000!访问控制authenticate(password:)校验密码示例中为pass后才实例化真实对象open(doors:)首先检查computer是否已授权——未授权时返回Access Denied. Im afraid I cant do that.致敬电影台词授权后才转发调用。代理模式最典型的应用场景就是权限校验、延迟加载、日志记录、远程访问等横切关注点把真实对象的生命周期与访问策略从业务逻辑中剥离出来。十、 Virtual Proxy 虚拟代理按需加载重量级对象模式定义见 virtual_proxy.swift代理模式提供一个引用底层对象的替身或占位对象虚拟代理用于按需加载对象。源码实现protocol HEVSuitMedicalAid { func administerMorphine() - String } final class HEVSuit: HEVSuitMedicalAid { func administerMorphine() - String { return Morphine administered. } } final class HEVSuitHumanInterface: HEVSuitMedicalAid { lazy private var physicalSuit: HEVSuit HEVSuit() func administerMorphine() - String { return physicalSuit.administerMorphine() } }调用示例let humanInterface HEVSuitHumanInterface() humanInterface.administerMorphine()原理剖析虚拟代理的核心思想是把昂贵对象的创建推迟到真正需要它的时刻Lazy Initialization。本例以《半条命》的 HEV 防护服为背景HEVSuit是被代理的重量级真实对象假设其构造代价高昂HEVSuitHumanInterface是代理关键实现是lazy private var physicalSuit: HEVSuit HEVSuit()lazy关键字保证HEVSuit的实例化延迟到administerMorphine()首次被调用时才发生——在let humanInterface HEVSuitHumanInterface()这一行重型对象尚未创建。保护代理与虚拟代理是对同一个 Proxy 模式的两种职责切分保护代理在调用前拦截控制能不能调虚拟代理在调用时兜底控制什么时候创建。两者都遵循同一协议、对客户端完全透明。十一、模式选型对照何时使用哪个结构型模式基于上述 8 个源码实例可将结构型模式的适用场景归纳如下模式核心问题仓库示例适用信号Adapter 适配器两个类型接口不兼容旧目标Float角度 → 新协议Double角度adapter.swift需要接入遗留代码/第三方库而不想改动对方Bridge 桥接抽象与实现耦合扩展呈乘法爆炸遥控器抽象与电器实现独立演化bridge.swift抽象与实现都需要独立扩展Composite 组合单个对象与对象集合操作不一致图形与白板的统一drawcomposite.swift存在树形递归结构UI 视图树、文件系统Decorator 装饰器功能组合爆炸、需运行时增强咖啡叠加牛奶与奶泡decorator.swift需要动态、可叠加地扩展对象能力Facade 外观子系统复杂、客户端耦合深包装UserDefaults为下标访问facade.swift需要为复杂子系统提供统一简化入口Flyweight 享元大量相似对象浪费内存咖啡产地对象按origin缓存共享flyweight.swift对象数量巨大且存在可共享的内在状态Protection Proxy 保护代理真实对象需访问控制未授权时拒绝开门protection_proxy.swift需要权限校验、安全检查等横切逻辑Virtual Proxy 虚拟代理重量级对象过早创建lazy延迟实例化防护服virtual_proxy.swift对象构造昂贵且可能根本用不到十二、如何运行与验证仓库为结构型模式提供了两条可运行的验证路径1. Xcode Playground 直接运行打开仓库根目录下的 Design-Patterns.playground在左侧导航进入Structural.xcplaygroundpage对应文件为 Design-Patterns.playground/Pages/Structural.xcplaygroundpage/Contents.swift。该页面依次包含上述全部模式的 Markup 说明与可执行代码点击运行即可在右侧时间轴看到各模式的输出如 Composite 的Drawing a circle with color Red、Flyweight 的Serving Yirgacheffe, Ethiopia to table 1等。页面顶部还提供了 Behavioral、Creational 两个章节的交叉导航链接。2. 源码 脚本重新生成各模式的独立源码位于 source/structural/其中imports.swift、startComment、startSwiftCode、endComment、endSwiftCode、footer.md等脚手架文件与 generate-playground.sh 脚本共同构成 playground 的生成流水线。修改任一*.swift模式源码后可重新执行生成脚本以同步更新 Playground 页面。此外仓库还提供中文版本 source-cn/structural/ 与 Design-Patterns-CN.playground便于对照阅读。3. 自定义验证建议如需快速验证某个模式的行为可复制对应*.swift文件到独立 Swift 文件在文件末尾追加断言语句或print输出验证 Adapter断言newFormat.angleH 14.0验证 Protection Proxy断言未认证时open返回Access Denied字符串认证后返回HAL9000的肯定答复验证 Decorator对比叠加前后cost与ingredients的累积变化。结语结构型设计模式解决的是软件设计中实体间关系如何组织这一根本问题。Design-Patterns-In-Swift 仓库用 8 个精炼的 Swift 5.0 源码示例把 Adapter 的接口转译、Bridge 的维度解耦、Composite 的递归树、Decorator 的运行时增强、Facade 的简化门面、Flyweight 的共享缓存以及两种 Proxy 的访问控制与延迟加载全部落实到可运行、可验证的代码之上。结合 source/structural/header.md 的模式定义与 Playground 的交互式运行环境读者既能从原理上理解每种模式的适用边界也能在真实 Swift 项目中直接参考这些实现组织自己的类结构——这正是结构型模式简化关系、清晰设计的价值所在。【免费下载链接】Design-Patterns-In-Swift Design Patterns implemented in Swift 5.0项目地址: https://gitcode.com/gh_mirrors/de/Design-Patterns-In-Swift创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表