普通视图

发现新文章,点击刷新页面。
今天 — 2026年3月2日首页

04 | 别再写几十个参数的构造函数了——建造者模式

2026年3月2日 17:14

不知道你有没有接手过那种“祖传代码”,里面有一个极其庞大的类,初始化的时候需要传十几个参数。每次调用它,你都得小心翼翼地数逗号:new User('张三', null, true, 18, null, 'admin', ...)

一旦中间少传了一个 null,或者把第 5 个参数和第 6 个参数搞反了,整个程序直接报错,查都查不出来。 我以前写这种代码的时候,自己都觉得心虚,生怕哪天把自己给坑了。

今天咱们聊的这个建造者模式(Builder Pattern),就是专门为了解决这种“参数地狱”而生的。

为什么我们总是被“参数列表”搞得晕头转向?

说白了,这种长参数列表的问题,在于我们试图一口吃成个胖子。 我们想在实例化的一瞬间,把所有属性都塞进去。

但这违背了人类的认知习惯。 想象一下你去赛百味(Subway)买三明治。 你不会一进门就冲着店员喊一串代码:“我要全麦面包加火腿加生菜去洋葱加蛋黄酱烤热带走!” 店员肯定懵圈。

正确的流程是分步骤: 先选面包,再选肉,然后选配菜,最后选酱料。 每一步都是独立的,你可以选,也可以不选。

建造者模式的底层逻辑就是:把一个复杂对象的“构建过程”和它的“部件”分离。 不再是一次性 new 出来,而是通过一个专门的“建造者”,一步一步地把对象组装起来。

怎么把代码写得像“点菜”一样优雅?

在 JavaScript 里,我们可以利用链式调用(Chaining),把这个模式实现得非常漂亮。

假设我们要创建一个复杂的 Request 对象,用来发网络请求。

如果不适用模式,代码是这样的:

// 参数太多,根本记不住哪个位置是干啥的
// 第三个参数是 timeout 还是 headers?完全靠猜
const req = new HttpRequest('https://api.com', 'POST', null, 5000, { 'Content-Type': 'json' });

现在,我们用建造者模式改造一下:

class RequestBuilder {
  constructor(url) {
    this.url = url;
    this.method = 'GET'; // 默认值
    this.headers = {};
    this.body = null;
  }

  setMethod(method) {
    this.method = method;
    return this; // 关键:返回 this,实现链式调用
  }

  setHeader(key, value) {
    this.headers[key] = value;
    return this;
  }

  setBody(data) {
    this.body = JSON.stringify(data);
    return this;
  }

  // 最后一步:产出真正的对象
  build() {
    // 这里还可以加校验逻辑,比如:如果是 POST,必须有 body
    if (this.method === 'POST' && !this.body) {
      throw new Error('POST 请求必须有 Body');
    }
    return {
      url: this.url,
      method: this.method,
      headers: this.headers,
      body: this.body
    };
  }
}

// 使用起来就像写文章一样流畅
const request = new RequestBuilder('https://api.com')
  .setMethod('POST')
  .setHeader('Authorization', 'Bearer xxx')
  .setBody({ name: '小美' })
  .build();

两种写法的直观对比

容易出问题的写法: new Class(a, b, c, d, e...) 后果:代码可读性极差,维护者必须对着文档数参数位置。如果中间要插入一个新参数,所有调用方都得改。

更稳健的建造者写法: .setA().setB().build() 后果:代码本身就是文档,读起来像英语句子。参数顺序无所谓,不想传的参数直接跳过,用默认值即可。

给你的 3 条行动建议

  1. 参数超过 4 个就该警惕了:如果你的构造函数参数超过 4 个,或者有好几个参数是可选的(经常传 null),别犹豫,马上换成建造者模式,或者至少用“配置对象”传参。

  2. 把校验逻辑放在 build 里:这是建造者模式最大的隐藏红利。你可以在 build() 方法里统一检查“A 属性存在时 B 属性是否也存在”,保证产出的对象永远是合法的。

  3. JS 的“配置对象”其实是简化版:在 JS 里,我们经常直接传一个对象 { url: '...', method: '...' }。这其实是建造者模式的一种“变体”。但如果你需要复杂的构建逻辑(比如根据 A 参数自动计算 B 参数),标准的 Builder 类还是更清晰。

我以前总觉得多写一个 Builder 类是增加代码量。 后来在一次重构中,我把一个 12 个参数的初始化函数改成了 Builder,那天下午我看着那段清晰的代码,心里那个舒坦。

代码是写给人看的,顺便给机器运行。 让调用者用得舒服,是你作为 API 设计者的温柔。

从UIKit到SwiftUI的迁移感悟:数据驱动的革命

作者 StarkCoder
2026年3月2日 15:40

前言

作为一名iOS开发者,我最近完成了一个项目从UIKit到SwiftUI的迁移。这个过程不仅仅是代码的重写,更是一种开发思维的转变。今天,我想分享一下这段旅程中的感悟和具体实践,希望能给正在考虑或正在进行类似迁移的开发者一些参考。

设计哲学的根本差异

UIKit:命令式的"导演"

在UIKit的世界里,我们是"导演",需要明确地告诉每一个UI元素如何表现:

  • 创建视图:let button = UIButton(type: .system)
  • 设置属性:button.setTitle("点击我", for: .normal)
  • 添加到父视图:view.addSubview(button)
  • 布局约束:button.translatesAutoresizingMaskIntoConstraints = false
  • 响应事件:button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)

每一步都需要我们显式地发出指令,控制UI的每一个细节。这就像是在指挥一场盛大的演出,每一个演员的动作都需要我们亲自指导。

SwiftUI:声明式的"编剧"

而在SwiftUI的世界里,我们更像是"编剧",只需要描述UI应该是什么样子:

Button("点击我") {
    // 点击事件处理
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)

我们不再关心视图是如何创建和布局的,只需要声明它的最终状态。SwiftUI会自动处理所有的底层实现,包括视图的创建、更新和销毁。

从命令驱动到数据驱动

最核心的转变是从"命令驱动"到"数据驱动"。在UIKit中,我们通过调用方法来改变UI状态;而在SwiftUI中,我们只需要修改数据,UI会自动响应数据的变化。

SwiftUI的属性包装器:新手友好指南

在深入具体案例之前,我想先介绍一下SwiftUI的属性包装器,这是理解SwiftUI数据流的关键。以下是项目中实际使用的属性包装器示例:

@State:管理视图内部状态

@State是最基础的属性包装器,用于管理视图内部的状态:

@State private var isToggleOn = false

var body: some View {
    Toggle("开关", isOn: $isToggleOn)
}

isToggleOn的值改变时,使用它的视图会自动重新渲染。

@Published:发布者属性

@Published用于标记ObservableObject中的属性,当属性值改变时,会通知所有订阅它的视图。在项目中,我们在多个管理器中使用了@Published

// AppState.swift
final class AppState: ObservableObject {
    static let shared = AppState()
    
    /// 是否需要重置应用
    @Published var resetApp = false
    
    // 其他代码...
}

// GlobalOverlayManager.swift
final class GlobalOverlayManager: ObservableObject {
    static let shared = GlobalOverlayManager()
    
    /// 当前显示的弹框类型
    @Published var current: OverlayType?
    
    // 其他代码...
}

// Router.swift
class Router: ObservableObject {
    
    // 当前选中的Tab
    @Published var selectedTab: MainTab = .home
    
    // 为每个tab单独存储NavigationPath
    @Published var homePath = NavigationPath()
    @Published var hotPath = NavigationPath()
    // 其他代码...
}

@StateObject:持久化的观察对象

@StateObject@ObservedObject类似,但它会在视图的整个生命周期中保持对象的存在,不会因为视图的重新渲染而创建新的实例。在项目中,我们在App入口和视图中使用了@StateObject

// EviApp.swift
@main
struct EviApp: App {
    // 应用状态管理器
    @StateObject private var appState = AppState.shared
    // 全局弹框管理器
    @StateObject private var overlay = GlobalOverlayManager.shared
    // 全局导航路由器
    @StateObject private var router = Router()
    
    // 其他代码...
}

// MainContainerView.swift
struct MainContainerView: View {
    @StateObject private var appConfigManager = AppConfigManager.shared
    
    // 其他代码...
}

@EnvironmentObject:全局共享对象

@EnvironmentObject用于在整个应用中共享数据,避免了层层传递数据的麻烦。在项目中,我们通过environmentObject方法注入全局对象,并在视图中使用@EnvironmentObject来访问:

// EviApp.swift
var body: some Scene {
    WindowGroup {
        MainContainerView()
            .environmentObject(router)
            .environmentObject(overlay)
            // 其他代码...
    }
}

// MainContainerView.swift
struct MainContainerView: View {
    @EnvironmentObject private var overlay: GlobalOverlayManager
    @EnvironmentObject private var router: Router
    
    // 其他代码...
}

迁移具体案例

1. 遮罩实现:从控制器弹出到ZStack

UIKit实现方式

在UIKit中,我们通常会创建一个遮罩视图,然后通过控制器的present方法将其显示在顶层:

let overlayViewController = OverlayViewController()
overlayViewController.modalPresentationStyle = .overFullScreen
overlayViewController.modalTransitionStyle = .crossDissolve
present(overlayViewController, animated: true, completion: nil)

SwiftUI实现方式

在SwiftUI中,我们使用ZStack来实现遮罩效果,更加简洁和声明式。以下是项目中实际的实现:

ZStack {
    // 真正负责页面生命周期的容器
    TabView(selection: $router.selectedTab) {
        tabView(.home)
        tabView(.hot)
        tabView(.creation)
        tabView(.style)
        tabView(.profile)
    }
    
    // 你的悬浮TabBar,根据当前选中标签的导航路径长度控制显示
    if isTabBarVisible {
        VStack {
            Spacer()
            FloatingTabBar(selectedTab: $router.selectedTab)
                .padding(.horizontal, 16)
                .padding(.bottom, 20)
        }
    }
    
    // 全局弹框显示
    if let current = overlay.current {
        
        // 遮罩
        Color.black.opacity(0.4)
            .ignoresSafeArea()
            .onTapGesture {
                overlay.dismiss()
            }
        
        switch current {
        case .login:
            LoginOverlayView(onClose: {
                overlay.dismiss()
            })
            .transition(.flipFromBottom)
        }
    }
}
.animation(.easeInOut(duration: 0.25), value: overlay.current)

这种方式的好处是:

  • 代码更加清晰,遮罩和内容在同一个视图层次结构中
  • 可以使用SwiftUI的动画系统,实现更流畅的过渡效果
  • 不需要管理控制器的生命周期

2. 重置App:从UIWindow重置到AppState管理

UIKit实现方式

在UIKit中,重置App通常需要通过重新设置UIWindow的根视图控制器来实现:

let window = UIApplication.shared.windows.first
window?.rootViewController = UINavigationController(rootViewController: LoginViewController())
window?.makeKeyAndVisible()

SwiftUI实现方式

在SwiftUI中,我们使用AppState来管理应用的重置状态,通过数据驱动UI的变化。以下是项目中实际的实现:

// AppState.swift
final class AppState: ObservableObject {
    static let shared = AppState()
    
    /// 是否需要重置应用
    @Published var resetApp = false
    
    private init() {}
    
    /// 触发应用重置
    func triggerReset() {
        resetApp = true
    }
    
    /// 完成重置,重置标志
    func completeReset() {
        resetApp = false
    }
}

// 在App入口处使用
@main
struct EviApp: App {
    // 把 AppDelegate 接进来,系统会照常调用 didFinishLaunchingWithOptions 等
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    // 应用状态管理器
    @StateObject private var appState = AppState.shared
    // 全局弹框管理器
    @StateObject private var overlay = GlobalOverlayManager.shared
    // 全局导航路由器
    @StateObject private var router = Router()

    var body: some Scene {
        WindowGroup {
            MainContainerView()
                .environmentObject(router)
                .environmentObject(overlay)
                .onChange(of: appState.resetApp) {
                    if appState.resetApp {
                        // 当需要重置时,调用 Router 的 reset 方法重置状态
                        router.reset()
                        // 重置完成后,重置标志,避免无限循环
                        appState.completeReset()
                    }
                }
        }
    }
}

这种方式的好处是:

  • 逻辑更加清晰,通过状态来控制UI的显示
  • 不需要直接操作UIWindow,更加符合SwiftUI的设计理念
  • 可以在任何地方通过AppState.shared.triggerReset()来触发重置

3. 路由管理:从控制器弹出到Router类控制

UIKit实现方式

在UIKit中,我们通常直接使用控制器的pushpresent方法来导航:

let detailViewController = DetailViewController()
navigationController?.pushViewController(detailViewController, animated: true)

SwiftUI实现方式

在SwiftUI中,我们使用Router类来管理所有标签页的导航路径,实现了标签页间的独立导航和状态保持。详细的实现代码和设计思路可以参考我之前的文章:SwiftUI路由管理架构揭秘:从混乱到优雅的蜕变

这种方式的好处是:

  • 集中管理所有的导航逻辑,更加清晰
  • 可以在任何地方通过Router来控制导航,不需要直接操作视图
  • 支持复杂的导航场景,如深链接

迁移过程中的挑战与收获

挑战

  1. 思维方式的转变:从命令式到声明式,需要一段时间适应
  2. API的差异:许多UIKit的API在SwiftUI中没有直接对应
  3. 第三方库的兼容性:一些UIKit的第三方库可能还没有SwiftUI版本

收获

  1. 代码量减少:SwiftUI的声明式语法大大减少了代码量
  2. 开发效率提高:不需要手动管理视图的创建和更新,开发速度更快
  3. 动画效果更简单:SwiftUI的动画系统非常强大,实现复杂动画变得容易
  4. 预览功能:SwiftUI的预览功能可以实时查看UI效果,提高开发效率

总结

从UIKit到SwiftUI的迁移,不仅仅是技术栈的变化,更是一种开发思维的转变。SwiftUI的声明式和数据驱动的设计理念,让我们能够更加专注于UI的外观和用户体验,而不是底层的实现细节。

虽然迁移过程中会遇到一些挑战,但当你习惯了SwiftUI的开发方式后,你会发现它给你带来的便利和效率提升是值得的。

最后,我想说:SwiftUI是iOS开发的未来,拥抱变化,享受数据驱动的革命吧!

昨天 — 2026年3月1日首页

Vibe Coding 全栈专业名词清单|设计模式·基础篇(创建型+结构型核心名词)

作者 _哆啦A梦
2026年3月1日 12:26

image.png

家人们!谁懂啊😭 做Vibe Coding全栈开发,一听到“设计模式”就头大,一堆专业名词像天书,刚记完单例,转头就忘了工厂方法是啥。这篇基础篇,就是来拯救小白的——不搞虚的,不堆专业废话,用打工人能听懂的大白话+写实场景,把“创建型+结构型”设计模式的核心名词,掰碎了喂给你,单篇读完也就15分钟,轻松拿捏入门知识点,再也不用被产品经理问“你这组件怎么创建的”时支支吾吾!

先唠句大实话:设计模式到底是啥?

image.png

说白了,设计模式就是“全栈打工人的摸鱼高效模板”——就像你点外卖,不用每次都自己买菜、做饭、洗碗(重复造轮子),直接点现成的(用设计模式),又快又不出错。而创建型、结构型,就是最基础的两类“模板”,一个管“怎么造对象”,一个管“怎么把对象拼起来用”,学会这俩,日常80%的简单开发需求,都能轻松hold住。

一、创建型设计模式(管“造对象”的模板)

image.png

核心逻辑:不用你每次都new对象,搞一堆重复代码,像工厂流水线一样,按需造对象,省时间、少出错,主打一个“懒出效率”。

1. 单例模式(全栈er最常用,没有之一)

image.png

专业名词解读:整个程序里,某个对象只造一次,不管你调用多少次,都是同一个“打工人”,不重复招人(new对象),避免浪费资源。

写实场景:就像公司的打印机📠 ——全公司几十号人,不用每个人都配一台打印机(重复new),就一台公用的,谁用都是这台,省成本、不混乱。在Vibe Coding里,全局配置、全局弹窗、请求拦截器,基本都用单例模式,比如你写Vue的全局组件,总不能每次调用都new一次吧?那页面直接崩给你看!

避坑小吐槽:别瞎用单例!比如你写表单组件,要是用单例,多个表单共用一个对象,填完一个表单,另一个表单的数据也跟着变,到时候测试小姐姐追着你改bug,你就知道错了😂

2. 工厂模式(简单工厂+工厂方法,懒人福音)

image.png

专业名词解读:专门搞“对象生产线”,你告诉它要啥类型的对象,它就给你造啥,不用你自己写一堆if-else判断,代码看起来更干净。简单工厂是“小作坊”,能造多种对象但不好扩展;工厂方法是“连锁店”,每种对象一个工厂,扩展起来更方便。

写实场景:就像你去奶茶店点单🥤 ——你不用自己动手做(new对象),告诉店员要“珍珠奶茶”“果茶”(指定对象类型),店员(工厂)就给你做出来。在Vibe Coding里,写组件渲染、接口请求适配不同环境(开发/测试/生产),用工厂模式最合适,比如根据环境不同,工厂自动返回对应的请求地址,不用你写一堆判断,后期改起来也方便。

3. 建造者模式(复杂对象的“定制师”)

image.png

专业名词解读:造那种“零件多、结构复杂”的对象,比如一个完整的用户信息对象,有姓名、年龄、地址、权限等多个属性,不用一次性把所有属性都传进去,分步建造,灵活又清晰。

写实场景:就像你去定制蛋糕🎂 ——不用一次性告诉老板“我要一个草莓味、6寸、奶油裱花、带插件的蛋糕”,可以分步来:先选口味,再选尺寸,再选装饰,最后确认,老板一步步给你做好。在Vibe Coding里,写复杂的表单提交、页面配置组件,用建造者模式,能避免传参太多导致的混乱,后期维护也能快速找到对应的“零件”。

4. 原型模式(对象的“复制粘贴”)

image.png

专业名词解读:不用重新new一个对象,而是复制一个已有的对象(原型),再稍微修改一下属性,就能得到一个新对象,适合需要大量相似对象的场景,省时间。

写实场景:就像你写周报📝 ——不用每次都从零开始写,复制上一周的周报(原型),修改一下工作内容、进度,就能快速生成新的周报。在Vibe Coding里,写列表渲染、批量创建相似组件(比如多个卡片组件,只有内容不同),用原型模式,能减少重复代码,提升渲染效率。

二、结构型设计模式(管“拼对象”的模板)

核心逻辑:对象造好了,怎么把它们组织起来,让结构更清晰、更灵活,方便后期扩展,就像搭积木一样,把零散的积木(对象)拼成好看又结实的造型(程序结构)。

1. 适配器模式(“翻译官”一样的存在)

image.png

专业名词解读:解决“接口不兼容”的问题,就像翻译官,把一个接口的格式,转换成另一个接口能看懂的格式,不用修改原有代码,就能让两个不兼容的对象一起工作。

写实场景:就像你去国外旅游🌍 ——你说中文,外国人说英文,没法直接沟通,找个翻译官(适配器),把中文翻译成英文,就能正常交流。在Vibe Coding里,最常见的就是接口适配,比如后端返回的字段是“userName”,而你前端用的是“name”,不用改后端接口,也不用改前端大量代码,写一个适配器,把“userName”转换成“name”,完美解决问题。

2. 装饰器模式(给对象“加buff”)

image.png

专业名词解读:不改变原有对象的核心功能,给它加一些额外的功能,就像给手机戴手机壳、贴钢化膜,手机本身的功能没变,但多了保护、装饰的作用,灵活又不破坏原有结构。

写实场景:就像你上班摸鱼💼 ——你本身的工作是写代码(核心功能),但你可以一边写代码,一边听音乐(额外功能),不影响写代码,还能提升摸鱼幸福感。在Vibe Coding里,写日志打印、权限校验、函数防抖节流,都能用装饰器模式,比如给一个接口请求函数加个装饰器,自动打印请求参数和返回结果,不用在每个函数里都写日志代码。

3. 代理模式(对象的“代言人”)

image.png

专业名词解读:不让你直接访问某个对象,而是通过一个“代言人”(代理对象)去访问,代理对象可以帮你做一些额外的操作,比如权限校验、缓存、日志,保护原对象的安全。

写实场景:就像你找房子🏠 ——你不用直接找房东,而是找中介(代理),中介帮你筛选房源、带看、谈价格,你只需要最终确认,中介还能帮你规避一些风险(比如房东不靠谱)。在Vibe Coding里,写缓存、权限控制、懒加载,都能用代理模式,比如图片懒加载,不用直接加载图片对象,而是通过代理对象,判断图片是否进入可视区域,再加载,提升页面性能。

4. 其他常用结构型模式(简单唠唠,不啰嗦)

组合模式:像文件夹一样,文件夹里可以放文件,也可以放子文件夹,用来处理树形结构,比如前端的菜单组件、树形控件,就是典型的组合模式。

外观模式:给复杂的系统套一个“简化接口”,就像手机的快捷指令,不用你一步步操作,点一下快捷指令,就能完成一系列复杂操作,比如前端的封装请求工具,把复杂的请求逻辑封装起来,只暴露一个简单的调用方法。

桥接模式、享元模式:日常用得相对少一点,不用死记,知道它们是“解决结构冗余、提升复用性”的就行,等用到的时候再回头查,主打一个“不浪费脑细胞”😎

基础篇小结(划重点,别偷懒)

创建型:管“造对象”,记住单例、工厂、建造者、原型,日常开发够用;结构型:管“拼对象”,重点掌握适配器、装饰器、代理,解决接口、功能、访问的问题。这篇基础篇,单独看也能轻松入门,不用依赖后面的内容,下次再被设计模式名词难住,就回头翻这篇,包你一看就懂!

❌
❌