Swift多实例间共享Builder类的实践疑问:单例是否可行?
Builder模式单例化的实践疑问解答
问题背景
我正在提升开发能力,开始在小型测试项目中学习使用设计模式。当前项目聚焦Builder模式,包含两个Storyboard视图、一个被所有视图复用的ViewController类,以及一个在ViewController中实现、用于创建含两个属性对象的Builder类。
代码示例:
class ViewController: UIViewController { @IBOutlet var startTimePicker: UIDatePicker! @IBOutlet var workingHoursPicker: UIDatePicker! @IBAction func setStartTimeButtonPressed(_ sender: Any) { setStartTime() } @IBAction func setWorkingHoursButtonPressed(_ sender: Any) { setWorkingHours() } var workdayBuilder: WorkdayBuilder = ConcreteWorkdayBuilder() // ... 其他代码
当前问题:每次通过Segue跳转视图时,ViewController会被重新实例化,导致之前设置的Builder值丢失。我知晓可通过传值或传递Builder实例解决,但认为这会破坏本项目中Builder模式的设计初衷。
于是尝试将Builder包装成单例以实现多视图共享:
final class WorkdayBuilderSingleton { static let sharedInstance: WorkdayBuilder = ConcreteWorkdayBuilder() init() {} }
但在ViewController中设置属性时遇到错误:Cannot assign to property: 'sharedInstance' is a 'let' constant。
现存在疑问:
- 将Builder类设计为单例是否属于可行的良好实践?
- 若可行,能否将static let改为static var而不破坏单例特性?
解答
1. Builder类设计为单例是否是良好实践?
得结合你的业务场景判断:
- 如果你的Workday对象是全局唯一的、贯穿整个流程的上下文(比如用户正在构建的当日工作配置,整个APP生命周期内只需要维护一份),用单例持有Builder是可行的,但要注意两个局限:
- Builder模式的核心是分步构建不同配置的对象,单例会让Builder的状态全局共享,意味着你无法同时构建多个不同的Workday实例,直接限制了Builder的灵活性。
- 单例的状态是全局可变的,容易引发意料之外的副作用(比如某个页面修改了Builder的属性,其他页面会直接感知到,调试时会增加复杂度)。
- 如果你只是想在多个ViewController间共享Builder的状态,其实更推荐用依赖注入或者通过Segue传递Builder实例——这并不会破坏Builder模式的初衷,反而符合单一职责:Builder负责构建对象,视图控制器负责收集用户输入并调用Builder,传递实例只是在不同视图间共享构建上下文而已。
2. 能否将static let改为static var而不破坏单例特性?
可以,但要注意正确的实现方式:
- 直接把
static let改成static var会让外部代码直接重新赋值sharedInstance,彻底破坏单例的唯一性(比如其他地方可以写WorkdayBuilderSingleton.sharedInstance = AnotherBuilder())。 - 正确的做法是把
sharedInstance设为private(set) static var,这样外部只能读取实例、修改实例内部属性,不能重新赋值整个实例:
final class WorkdayBuilderSingleton { // 外部只能读取实例,不能重新赋值整个对象 private(set) static var sharedInstance: WorkdayBuilder = ConcreteWorkdayBuilder() // 私有化构造器,防止外部手动实例化 private init() {} }
你之前遇到的错误,本质是试图重新给sharedInstance这个常量赋值,而不是修改它内部的属性。改成上面的写法后,就可以正常调用WorkdayBuilderSingleton.sharedInstance.setStartTime(...)这类方法修改内部状态了。
另外,如果你不需要全局唯一的Builder,只是跨页面共享状态,也可以考虑用ViewModel层持有Builder,通过Segue传递ViewModel实例,这样既隔离了状态,又比单例更灵活。
内容的提问来源于stack exchange,提问作者bkDev
相关产品推荐
相关产品推荐

