在这个例子中如何限制可变性的风险?
How to limit the risks of mutability in this example?
我的代码类似于:
fun verify(problem,answer){
val errors: MutableList<String> = mutableListOf()
if (!condiditon_1) {
errors.add("Condition_1 is not verified")
}
if (!condition_2) {
errors.add("Condition_2 is not verified")
}
return errors
}
这里errors
是一个可变列表。我正在尝试重写限制可变性的程序
第一种方式是:
errorsImmutable = errors.toList()
和 return 不可变列表 errorsImmutable
以防止从外部对其进行修改。
另一种解法是:
fun verify(problem,answer){
val errors: List<String> = emptyList()
if (!condiditon_1) {
val mutableList = errorLogs.toMutableList()
mutableList.add("Condition_1 is not verified")
errors = mutableList.toList()
}
if (!condition_2) {
val mutableList = errorLogs.toMutableList()
mutableList.add("Condition_2 is not verified")
errors = mutableList.toList()
}
return errors
}
哪个更好。更一般地说,有更好的方法吗?
您在示例中忽略了显示函数的 return 类型。简单地 return 一个只读列表并相信消费者不会将其转换为 MutableList 来修改它通常被认为是好的。
fun verify(): List<String> {
val errors: MutableList<String> = mutableListOf()
if (!condiditon_1) {
errors.add("Condition_1 is not verified")
}
if (!condition_2) {
errors.add("Condition_2 is not verified")
}
return errors
}
如果不编写自己的不可变列表 class,就没有创建真正不可变列表的干净方法。请注意,标准库的 listOf
函数 return 是只读列表,而不是不可变列表,具有单个元素参数的重载除外。
一般来说,如果您可以创建一个已经填充的集合,这往往比创建一个空集合然后填充它要好——原因有很多,包括您所说的不变性。当然,这并不总是可能的(或优雅的),但在这种情况下,这是一种方法:
fun verify() = listOfNotNull(
if (condition1) "Condition1 is not verified" else null,
if (condition2) "Condition2 is not verified" else null,
)
或等价物:
fun verify() = listOfNotNull(
"Condition1 is not verified".takeIf{ condition1 },
"Condition2 is not verified".takeIf{ condition2 },
)
这样你就永远不会看到对列表的可变引用(尽管 listOfNotNull()
实现中可能涉及一个)。因为列表现在是单个表达式,您可以使用表达式主体函数,并让它推断类型。 (当然,更短的代码并不 总是 更简单、更清晰或更易于维护,但我认为这里可能是——当然取决于周围的代码。无论哪种方式,它通常是有用的学习练习。)
这里有一个更具声明性的方法,它单独定义条件(并分解出通用消息格式):
val conditions = listOf(
Pair({ condition1 }, "Condition1"),
Pair({ condition2 }, "Condition2"),
)
fun verify() = conditions.filter{ it.first() }
.map{ "${it.second} is not verified" }
PS。我错过了另一个调整:如果您使用 to
而不是明确指定 Pair
,它可能会稍微更具可读性:
val conditions = listOf(
{ condition1 } to "Condition1",
{ condition2 } to "Condition2",
)
我的代码类似于:
fun verify(problem,answer){
val errors: MutableList<String> = mutableListOf()
if (!condiditon_1) {
errors.add("Condition_1 is not verified")
}
if (!condition_2) {
errors.add("Condition_2 is not verified")
}
return errors
}
这里errors
是一个可变列表。我正在尝试重写限制可变性的程序
第一种方式是:
errorsImmutable = errors.toList()
和 return 不可变列表 errorsImmutable
以防止从外部对其进行修改。
另一种解法是:
fun verify(problem,answer){
val errors: List<String> = emptyList()
if (!condiditon_1) {
val mutableList = errorLogs.toMutableList()
mutableList.add("Condition_1 is not verified")
errors = mutableList.toList()
}
if (!condition_2) {
val mutableList = errorLogs.toMutableList()
mutableList.add("Condition_2 is not verified")
errors = mutableList.toList()
}
return errors
}
哪个更好。更一般地说,有更好的方法吗?
您在示例中忽略了显示函数的 return 类型。简单地 return 一个只读列表并相信消费者不会将其转换为 MutableList 来修改它通常被认为是好的。
fun verify(): List<String> {
val errors: MutableList<String> = mutableListOf()
if (!condiditon_1) {
errors.add("Condition_1 is not verified")
}
if (!condition_2) {
errors.add("Condition_2 is not verified")
}
return errors
}
如果不编写自己的不可变列表 class,就没有创建真正不可变列表的干净方法。请注意,标准库的 listOf
函数 return 是只读列表,而不是不可变列表,具有单个元素参数的重载除外。
一般来说,如果您可以创建一个已经填充的集合,这往往比创建一个空集合然后填充它要好——原因有很多,包括您所说的不变性。当然,这并不总是可能的(或优雅的),但在这种情况下,这是一种方法:
fun verify() = listOfNotNull(
if (condition1) "Condition1 is not verified" else null,
if (condition2) "Condition2 is not verified" else null,
)
或等价物:
fun verify() = listOfNotNull(
"Condition1 is not verified".takeIf{ condition1 },
"Condition2 is not verified".takeIf{ condition2 },
)
这样你就永远不会看到对列表的可变引用(尽管 listOfNotNull()
实现中可能涉及一个)。因为列表现在是单个表达式,您可以使用表达式主体函数,并让它推断类型。 (当然,更短的代码并不 总是 更简单、更清晰或更易于维护,但我认为这里可能是——当然取决于周围的代码。无论哪种方式,它通常是有用的学习练习。)
这里有一个更具声明性的方法,它单独定义条件(并分解出通用消息格式):
val conditions = listOf(
Pair({ condition1 }, "Condition1"),
Pair({ condition2 }, "Condition2"),
)
fun verify() = conditions.filter{ it.first() }
.map{ "${it.second} is not verified" }
PS。我错过了另一个调整:如果您使用 to
而不是明确指定 Pair
,它可能会稍微更具可读性:
val conditions = listOf(
{ condition1 } to "Condition1",
{ condition2 } to "Condition2",
)