我知道有两种通用方法,它们有很大的不同。您必须为任务选择适当的机制——在标准的基于类的 OO 语言(例如 Java/C++/C#/Python)中,它们是我所知道的仅有的两种方法。(我不熟悉的不同范式中可能还有其他方法。)
1.检查状态。
这已经在许多必须跟踪系统/支持资源状态的类中完成。两个常见的例子是(文件)流和数据库连接。
“模板”可能如下所示:
void Logon(credentials) { ..; loggedOn = true }
void DieUnlessLoggedIn { if (!loggedOn) { throw .. } }
void DoStuff () { DieUnlessLoggedIn(); .. }
虽然上述方法非常通用,但某些语言可能支持不变量(Eiffel)、装饰(Python)、注释、AOP 或其他断言机制。
这种方法对于可变世界中的动态状态很有用:例如“注销”之后会发生什么?状态DoStuff
再次无效,直到重新登录(如果允许)。但是,这种方法通常不能用于主流 OOP 语言中的编译时检查,因为运行时状态在编译时根本不可用。
2. 使用多种类型来表示状态。
创建两个单独的类型,例如 ServiceLogon (method Logon
) 类型创建 ServiceAccess (method DoStuff
)。因此DoStuff
只能在从Logon
(在 ServiceLogon 上)创建之后调用(在 ServiceAccess 类型上)。这可以很好地在具有成员隐藏的静态语言中强制执行调用顺序语义 - 因为如果错误,程序将无法编译。
login = new ServiceLogon(credentials)
access = login.Logon();
access.DoStuff(); // can't be called before obtained via Logon
使用类型编码附加状态可能过于复杂,因为它会破坏基于类的类型系统,但在“构建器”和“存储库”模式等中很有用;基本上,询问类型是否需要拆分以维护 SRP,然后考虑这种方法。
如果不结合状态检查,这种方法无法完全处理“注销”之类的事情,因为类型 ServiceAccess(在干净的意义上)总是表示相同的状态,因为它被编码在类型中。
1. & 2. 使用状态检查和状态/角色特定类型。
当然,混合是完全可以接受的,上述两种方法并不相互排斥。分离使一种类型(以及由此调用的方法)依赖于另一种方法的角色可能是有意义的,同时仍适当地检查运行时状态。如上所述,#1 非常适合运行时守卫(可以是高度动态的),而#2 可以在编译时强制执行某些规则。