状态模式

安卓开发中状态模式的应用

一、状态模式的基本概念

状态模式(State Pattern),又称状态对象模式(State Object Pattern),是一种行为型设计模式,它允许一个对象在其内部状态改变时改变其行为。这个对象看上去就像是改变了它的类一样

状态模式有以下三个角色:

  • Context(环境):定义了客户端需要的接口,并维护一个State的实例,将与状态相关的操作委托给当前的ConcreteState对象来处理。
  • State(抽象状态):定义一个接口,以封装使用Context的一个特定状态相关的行为。
  • ConcreteState(具体状态):实现抽象状态定义的接口。

状态模式的UML图如下:

二、安卓源码中的状态模式实例

安卓源码中有很多使用状态模式的例子,例如WifiManager、AudioManager、TelephonyManager等。这里我们以WifiManager为例,来分析它是如何使用状态模式来管理Wifi连接和断开的过程。

WifiManager是一个系统服务,它提供了管理Wifi连接和扫描附近热点的功能。WifiManager内部有一个名为WifiStateMachine的类,它是一个有限状态机(Finite State Machine),用来控制Wifi连接和断开的逻辑。WifiStateMachine继承自StateMachine类,StateMachine类是一个通用的有限状态机框架,它提供了创建和切换状态的方法。

WifiStateMachine定义了以下几个内部类,分别对应不同的Wifi连接状态:

  • DefaultState:默认状态,所有消息都会先经过这个状态处理。
  • InitialState:初始状态,当WifiStateMachine创建时进入这个状态。
  • SupplicantStartingState:Supplicant启动状态,当打开Wifi时进入这个状态。
  • SupplicantStartedState:Supplicant已启动状态,当Supplicant启动成功时进入这个状态。
  • DriverStartingState:驱动启动状态,当开始扫描热点时进入这个状态。
  • DriverStartedState:驱动已启动状态,当驱动启动成功时进入这个状态。
  • ScanModeState:扫描模式状态,当处于扫描热点的过程中时进入这个状态。
  • ConnectModeState:连接模式状态,当准备连接某个热点时进入这个状态。
  • L2ConnectedState:L2层已连接状态,当与某个热点建立L2层连接时进入这个状态。
  • ObtainingIpState:获取IP地址状态,当从某个热点获取IP地址时进入这个状态。
  • VerifyingLinkState:验证链接状态,当验证IP地址是否有效时进入这个状态。
  • CaptivePortalCheckState:检测门户网站(Captive Portal)状态,当检测是否需要登录门户网站时进入这个状态。
  • ConnectedState:已连接状态,当成功连接某个热点并获取网络访问权限时进入这个状态。
  • RoamingState:漫游(Roaming)状态,当从一个热点切换到另一个热点时进入这个状态。
  • DisconnectingState:断开连接中状态,当主动或被动断开某个热点时进入这个

状态模式的优点是:

  • 封装了状态转换规则,使状态转换更加清晰和安全。
  • 将所有与某个状态有关的行为放到一个类中,方便增加新的状态和修改状态行为。
  • 避免使用大量的条件判断语句,提高代码的可读性和可维护性。

状态模式的缺点是:

  • 增加了系统中类和对象的个数,增加了系统的复杂度。
  • 对开闭原则的支持不太好,如果要增加新的状态或者修改状态转换条件,需要修改源代码。

2.1 Kotlin使用密封类实现

密封类或密封接口可以限制类的继承结构,保证类型安全和可维护性。可以用来实现状态模式,因为它们可以表示一个有限的状态集合,而且可以使用when表达式来匹配不同的状态,并执行相应的操作。

下面是一个使用Kotlin密封类实现状态模式的例子,它模拟了一个电视机和遥控器,电视机有三种状态:开机、关机和待机,每种状态下可以执行不同的操作。

kotlin 复制代码
// 密封类
sealed class TVState {
    // 开机状态
    object PowerOn : TVState() {
        fun show() {
            println("电视机已开机,显示画面")
        }
    }

    // 关机状态
    object PowerOff : TVState() {
        fun hide() {
            println("电视机已关机,隐藏画面")
        }
    }

    // 待机状态
    object Standby : TVState() {
        fun dim() {
            println("电视机已待机,画面变暗")
        }
    }
}
kotlin 复制代码
// 环境类
class TV(var state: TVState) { // 维持一个密封类对象的引用
    fun powerOn() { // 开机操作
        when (state) { // 根据不同的状态执行不同的操作
            is TVState.PowerOn -> state.show()
            is TVState.PowerOff -> {
                state = TVState.PowerOn // 切换到开机状态
                state.show()
            }
            is TVState.Standby -> {
                state = TVState.PowerOn // 切换到开机状态
                state.show()
            }
        }
    }

    fun powerOff() { // 关机操作
        when (state) { // 根据不同的状态执行不同的操作
            is TVState.PowerOn -> {
                state = TVState.PowerOff // 切换到关机状态
                state.hide()
            }
            is TVState.PowerOff -> state.hide()
            is TVState.Standby -> {
                state = TVState.PowerOff // 切换到关机状态
                state.hide()
            }
        }
    }

    fun standby() { // 待机操作
        when (state) { // 根据不同的状态执行不同的操作
            is TVState.PowerOn -> {
                state = TVState.Standby // 切换到待机状态
                state.dim()
            }
            is TVState.PowerOff -> state.hide()
            is TVState.Standby -> state.dim()
        }
    }
}
kotlin 复制代码
// 遥控器类
class RemoteControl(private val tv: TV) { // 持有一个环境对象的引用
    fun pressPowerButton() { // 按下电源键操作
        tv.powerOn() // 调用环境对象的开机操作 原创作者:掘金-长点点
    }

    fun pressStandbyButton() { // 按下待机键操作
        tv.standby() // 调用环境对象的待机操作
    }

    fun pressPowerOffButton() { // 按下关机键操作
        tv.powerOff() // 调用环境对象的关机操作
    }
}
kotlin 复制代码
fun main() {
    val tv = TV(TVState.PowerOff) // 创建环境对象,初始为关机状态
    val remoteControl = RemoteControl(tv) // 创建遥控器对象,持有环境对象的引用
    remoteControl.pressPowerButton() // 按下电源键操作
    remoteControl.pressStandbyButton() // 按下待机键操作
    remoteControl.pressPowerOffButton() // 按下关机键操作
}

输出结果:

复制代码
电视机已开机,显示画面
电视机已待机,画面变暗
电视机已关机,隐藏画面

2.2 Kotlin中的状态模式加入协程

Kotlin 复制代码
// 定义一个枚举类,表示电视机的状态
enum class TVState {
    // 开机状态
    PowerOn {
        override fun handle() {
            println("电视机已开机,显示画面")
        }
    },

    // 关机状态
    PowerOff {
        override fun handle() {
            println("电视机已关机,隐藏画面")
        }
    },

    // 待机状态
    Standby {
        override fun handle() {
            println("电视机已待机,画面变暗")
        }
    };

    // 定义一个抽象方法,用来执行不同状态下的操作
    abstract fun handle()
}
Kotlin 复制代码
// 定义一个协程上下文元素,用来保存和恢复电视机的状态
class TVContextElement(var state: TVState) : AbstractCoroutineContextElement(Key) {
    companion object Key : CoroutineContext.Key<TVContextElement>
}

// 定义一个协程作用域的扩展函数,用来创建一个带有电视机状态的协程
fun CoroutineScope.tvCoroutine(state: TVState, block: suspend CoroutineScope.() -> Unit): Job {
    return launch(TVContextElement(state)) {
        block()
    }
}

// 定义一个协程上下文元素的扩展函数,用来切换电视机的状态
suspend fun TVContextElement.switchState(newState: TVState) {
    state = newState
}

// 定义一个挂起函数,用来执行电视机的操作
suspend fun handleTV() {
    // 获取当前协程上下文中的电视机状态元素,并执行对应状态下的操作
    coroutineContext[TVContextElement.Key]?.state?.handle()
}
Kotlin 复制代码
// 定义一个遥控器类,用来控制电视机的协程
class RemoteControl(private val tvJob: Job) {
    fun pressPowerButton() { // 按下电源键操作
        GlobalScope.launch { // 在全局作用域中启动一个新的协程
            tvJob.join() // 等待电视机协程完成
            tvCoroutine(TVState.PowerOn) { // 创建一个新的带有开机状态的电视机协程
                handleTV() // 执行开机操作
            }
        }
    }

    fun pressStandbyButton() { // 按下待机键操作
        GlobalScope.launch { // 在全局作用域中启动一个新的协程
            tvJob.join() // 等待电视机协程完成
            tvCoroutine(TVState.Standby) { // 创建一个新的带有待机状态的电视机协程
                handleTV() // 执行待机操作
            }
        }
    }

    fun pressPowerOffButton() { // 按下关机键操作
        GlobalScope.launch { // 在全局作用域中启动一个新的协程
            tvJob.join() // 等待电视机协程完成
            tvCoroutine(TVState.PowerOff) { // 创建一个新的带有关机状态的电视机协程
                handleTV() // 执行关机操作
            }
        }
    }
}
Kotlin 复制代码
fun main() {
    val tvJob = GlobalScope.tvCoroutine(TVState.PowerOff) {} // 创建一个初始为关机状态的电视机协程
    val remoteControl = RemoteControl(tvJob) // 创建遥控器对象,持有电视机协程的引用
    remoteControl.pressPowerButton() // 按下电源键操作
    remoteControl.pressStandbyButton() // 按下待机键操作
    remoteControl.pressPowerOffButton() // 按下关机键操作
    GlobalScope.cancel() // 取消全局作用域中的所有协程
}
复制代码
电视机已开机,显示画面
电视机已待机,画面变暗
电视机已关机,隐藏画面

这个例子中,我使用了Kotlin协程中的状态机的思想,将电视机的状态保存在一个协程上下文元素中,然后使用一个协程构建器来创建一个带有电视机状态的协程,再使用一个挂起函数来切换和执行电视机的状态。遥控器类则是用来控制电视机协程的启动和取消的。这样,每次按下遥控器的按钮时,都会创建一个新的电视机协程,并根据不同的状态执行不同的操作。

2.3 Java接口实现

java 复制代码
// Java实现

// 抽象状态类
interface LightState {
    void handle(Light light); // 处理操作
}
java 复制代码
// 开灯状态类
class LightOnState implements LightState {
    @Override
    public void handle(Light light) {
        System.out.println("灯已经开了");
        light.setState(new LightOffState()); // 切换到关灯状态
    }
}
java 复制代码
// 关灯状态类
class LightOffState implements LightState {
    @Override
    public void handle(Light light) {
        System.out.println("灯已经关了");
        light.setState(new LightOnState()); // 切换到开灯状态
    }
}
java 复制代码
// 环境类
class Light {
    private LightState state; // 维持一个抽象状态对象的引用

    public Light(LightState state) { // 构造函数
        this.state = state;
    }

    public void setState(LightState state) { // 设置新状态
        this.state = state;
    }

    public void pressSwitch() { // 按下开关操作
        state.handle(this); // 调用状态方法
    }
}
java 复制代码
public class Main {
    public static void main(String[] args) {
        Light light = new Light(new LightOffState()); // 创建环境对象,初始为关灯状态
        light.pressSwitch(); // 按下开关操作
        light.pressSwitch(); // 按下开关操作
    }
}

输出结果:

复制代码
灯已经关了
灯已经开了

2.4 C++实现

下面是一个使用C++实现的状态模式的例子,它模拟了一个电视机遥控器,电视机有两种状态:开机和关机,每种状态下可以执行不同的操作。

c++ 复制代码
// 抽象状态类
class TVState {
public:
    virtual void handle() = 0; // 处理操作
};

// 开机状态类
class PowerOnState : public TVState {
public:
    void handle() {
        cout << "电视机已开机" << endl;
    }
};

// 关机状态类
class PowerOffState : public TVState {
public:
    void handle() {
        cout << "电视机已关机" << endl;
    }
};

// 环境类
class TVContext {
private:
    TVState* state; // 维持一个抽象状态对象的引用
public:
    TVContext() : state(nullptr) {} // 默认构造函数
    TVContext(TVState* state) : state(state) {} // 构造函数
    ~TVContext() { delete state; } // 析构函数
    void setState(TVState* state) { // 设置新状态
        delete this->state; // 释放旧状态
        this->state = state; // 更改状态
    }
    TVState* getState() { return state; } // 获取当前状态
    void powerOn() { // 开机操作
        setState(new PowerOnState()); // 设置开机状态
        state->handle(); // 调用状态方法
    }
    void powerOff() { // 关机操作
        setState(new PowerOffState()); // 设置关机状态
        state->handle(); // 调用状态方法
    }
};

int main() {
    TVContext* tv = new TVContext(); // 创建环境对象
    tv->powerOn(); // 开机操作
    tv->powerOff(); // 关机操作
    delete tv; // 释放环境对象
    return 0;
}

输出结果:

复制代码
电视机已开机
电视机已关机

三、与其他相似设计模式对比

  • 策略模式 让对象可以根据自身的选择或配置来选择不同的算法,实现动态地改变对象的行为。
  • 模板方法模式 让子类可以在不改变算法结构的情况下重写某些步骤,实现代码的复用和扩展。
  • 命令模式 将一个请求封装为一个对象,实现请求和执行的解耦和撤销/重做功能。
设计模式 目的 优点 缺点 使用场景
策略模式 定义一组可互换的算法,让对象可以根据自身的选择或配置来选择不同的算法 可以动态地改变对象的行为,增加了对象的灵活性和可扩展性,避免了多重条件语句 增加了系统中类和对象的个数,客户端必须知道不同的算法和它们之间的区别 当一个对象有多种行为,而且这些行为可以相互替换时,例如不同的排序算法、压缩算法、加密算法等
模板方法模式 定义一个算法的骨架,让子类可以在不改变算法结构的情况下重写某些步骤 可以实现代码复用,增加了子类的灵活性和可扩展性,遵循了开闭原则 增加了系统中类和对象的个数,可能导致子类过多或过于复杂,增加了维护成本 当一个算法有固定的步骤,而且这些步骤中有一些可以由子类来实现时,例如不同类型的文档、游戏、窗口等
命令模式 将一个请求封装为一个对象,让不同的请求者或接收者来执行这个请求 可以实现请求和执行的解耦,增加了请求者和接收者之间的灵活性和可扩展性,支持撤销和重做操作 增加了系统中类和对象的个数,可能导致命令过多或过于复杂,增加了维护成本 当需要将请求者和执行者分开,或者需要支持撤销和重做操作时,例如遥控器、撤销/重做、宏命令等
相关推荐
网安Ruler1 小时前
代码审计-PHP专题&原生开发&SQL注入&1day分析构造&正则搜索&语句执行监控&功能定位
android
paid槮2 小时前
MySql基础:数据类型
android·mysql·adb
用户2018792831674 小时前
AMS和app通信的小秘密
android
用户2018792831674 小时前
ThreadPoolExecutor之市场雇工的故事
android
诺诺Okami4 小时前
Android Framework-Launcher-InvariantDeviceProfile
android
Antonio9155 小时前
【音视频】Android NDK 与.so库适配
android·音视频
sun00770014 小时前
android ndk编译valgrind
android
AI视觉网奇15 小时前
android studio 断点无效
android·ide·android studio
jiaxi的天空15 小时前
android studio gradle 访问不了
android·ide·android studio
No Silver Bullet16 小时前
android组包时会把从maven私服获取的包下载到本地吗
android