我是否正确使用状态模式?

时间:2018-11-28 22:30:25

标签: swift state-machine

我正在学习状态模式(有限状态机) 在构建的示例项目中,我确定要更新UI的唯一方法是将当前视图的引用传递给状态机,然后从正在工作的状态更新UI。我做错了吗?

这是我的状态机

class CapturePhotoStateMachine {
    var noPictureTakenState: NoPictureTakenState?
    var pictureTakenState: PictureTakenState?
    var initialState: InitialState?
    var vc: SignupAvatarView?

    var capturePhotoState: CapturePhotoState?

    init(viewController: SignupAvatarView) {
        noPictureTakenState = NoPictureTakenState(stateMachine: self)
        pictureTakenState = PictureTakenState(stateMachine: self)
        initialState = InitialState(stateMachine: self)
        vc = viewController
        capturePhotoState = initialState
    }

    func setCapturePhotoState(newState: CapturePhotoState) {
        self.capturePhotoState = newState
    }

    func takePicture() {
        self.capturePhotoState?.takePicture()
    }

    func savePicture(image: UIImage) {
        self.capturePhotoState?.savePicture(image: image)
    }


    func retakePicture() {
        self.capturePhotoState?.retakePicture()
    }

    func setup() {
        self.capturePhotoState?.setup()
    }
}

这是我的协议

protocol CapturePhotoState {
    func takePicture()
    func savePicture(image: UIImage)
    func retakePicture()
    func setup()
}

这是国家的子类

class NoPictureTakenState: CapturePhotoState {

    var stateMachine: CapturePhotoStateMachine?

    init(stateMachine: CapturePhotoStateMachine) {
        self.stateMachine = stateMachine
    }

    func takePicture() {
        stateMachine!.vc?.previewView.isHidden = true
        stateMachine!.vc?.capturedImage.isHidden = false
        stateMachine!.vc?.saveButton.isHidden = false
        stateMachine!.vc?.retakePhoto.isHidden = false
        stateMachine?.setCapturePhotoState(newState: (stateMachine?.pictureTakenState)!)
    }

    func savePicture(image: UIImage) {
    }

    func retakePicture() {}

    func setup() {}
}

1 个答案:

答案 0 :(得分:1)

状态机用途的关键似乎是您具有要根据状态启用或禁用的接口对象。启用/禁用应该是视图控制器的工作。国家本身就是可以回答诸如“当前情况是什么”和“下一步应该发生什么”之类的问题的基础。

这是一个简短的简单状态机示例,用于说明。这是故意的。我们只有两个按钮,只有两个状态。在每种状态下,仅应启用一个按钮。状态由枚举的大小写表示,只要状态发生变化,我们就在该枚举上使用setter观察器进行响应。枚举封装了存在多少状态以及下一个状态是什么的逻辑,而视图控制器在状态更改和接口更改之间进行调解:

class ViewController: UIViewController {
    @IBOutlet weak var takePictureButton: UIButton!
    @IBOutlet weak var deletePictureButton: UIButton!
    @IBOutlet weak var pictureImageView: UIImageView! // not used in the example

    @IBAction func doTakePicture(_ sender: Any) {
        // do stuff
        doNextState()
    }

    @IBAction func doDeletePicture(_ sender: Any) {
        // do stuff
        doNextState()
    }

    enum State {
        case pictureNotTaken
        case pictureTaken
        var nextState : State {
            switch self {
            case .pictureNotTaken:
                return .pictureTaken
            case .pictureTaken:
                return .pictureNotTaken
            }
        }
    }
    var state : State = .pictureNotTaken {
        didSet {
            updateInterface()
        }
    }
    func doNextState() {
        self.state = self.state.nextState // triggers the setter observer
    }
    func updateInterface() {
        switch state {
        case .pictureNotTaken:
            takePictureButton.isEnabled = true
            deletePictureButton.isEnabled = false
        case .pictureTaken:
            takePictureButton.isEnabled = false
            deletePictureButton.isEnabled = true
        }
    }
}

可能您想要的是该模式的扩展。

  

我想出的更新UI的唯一方法是将当前视图的引用传递给状态机

上述模式就是这样做的。设置员观察员为我们解决了这个问题。


现在,您可能会反对switch中的updateInterface语句进行了错误的工作。它在界面控制器中找到有关界面如何完全反映状态的知识。您的冲动是肯定要知道知识是 state 的一部分(这就是为什么您以这种方式构造代码的原因)。

我的答复是:好,是的,不是。有时我确实有这种感觉,而解决问题的方法是为状态机赋予属性,该属性表达视图控制器可能对接口当前状态意味着的所有疑问。这样,知识会移到状态,但是接口仍然由视图控制器正确地控制。

因此,例如,我们可以将这两个属性添加到State枚举中:

enum State {
    // ... everything else is as before ...
    var userCanTakePicture : Bool { return self == .pictureNotTaken }
    var userCanDeletePicture : Bool { return self == .pictureTaken }
}

因此,现在,我们的updateInterface不需要任何有关每个州含义的专门知识;它只是询问状态 interface 应该是什么,它更简单并且可能会给出更令人满意的分权:

func updateInterface() {
    self.takePictureButton.isEnabled = state.userCanTakePicture
    self.deletePictureButton.isEnabled = state.userCanDeletePicture
}