引言
密封类是一种特殊的类,它用来表示受限的类继承结构,即一个类只能有有限的几种子类,而不能有任何其他类型的子类。
这不就是JAVA的枚举么。
概念
密封类使用sealed关键字声明,
在Kotlin 1.0中,密封类的所有子类必须嵌套在密封类内部;
在Kotlin 1.1中,这个限制放宽了,允许将子类定义在同一个文件中;
在Kotlin 1.5中,这个限制进一步放宽了,允许将子类定义在任何地方,只要保证子类可见性不高于父类。
密封类通常用于代替枚举类。密封类的优点在于可以更灵活地定义子类,而枚举类的每个成员都是固定的。密封类还可以帮助你编写更加类型安全的代码。
密封类最大的优点是可以配合when表达式实现完备性检查(exhaustive check),即编译器可以检测出是否覆盖了所有可能的分支情况,如果没有则会报错或提示警告。这样可以避免遗漏某些情况导致逻辑错误或运行时异常。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
很抽象。
密封类和枚举的区别是什么?
相同点:所有成员都属于本类本身
不同点:枚举只能有一个实例,而密封类的子类可以有多个实例。
密封类的子类可以携带自己独有的状态参数以及行为方法来记录更多的实现信息以完成更多的功能,这是枚举类所不具备的。
例子:
Kotlin
sealed class MyScore {
object FAIL_A_GRADE : MyScore()//分数差
object PASS_THE_MARK : MyScore()//分数及格
object SCORE_WELL : MyScore()//分数良好
class EXCELLENT_MARKS(val name: String) : MyScore()//分数优秀
}
密封类的所有成员都继承本类MyScore ,但是允许个别成员拥有自己特殊属性如 EXCELLENT_MARKS 。
三、使用
密封类的代数数据类型
密封类与 when 结合,使用 is 进行判断类型,跟枚举类相似
Kotlin
fun show() = when (myScore) {
is MyScore.FAIL_A_GRADE -> "分数差"
is MyScore.PASS_THE_MARK -> "分数及格"
is MyScore.SCORE_WELL -> "分数良好"
is MyScore.EXCELLENT_MARKS -> "分数优秀,name=${myScore.name}"
}
密封类实例
当某个成员需要特殊属性的时候,用枚举就比较难实现这个需求,密封类就是为了解决这个问题而出现的。
如下实例:当我们想知道优秀学生的名字的时候,只用枚举去实现就不好实现
Kotlin
//密封类,所有成员都继承本类
//当某个成员需要特殊属性的时候,用枚举就比较难实现这个需求
sealed class MyScore {
object FAIL_A_GRADE : MyScore()//分数差
object PASS_THE_MARK : MyScore()//分数及格
object SCORE_WELL : MyScore()//分数良好
class EXCELLENT_MARKS(val name: String) : MyScore()//分数优秀
}
class Teachers(private val myScore: MyScore) {
fun show() = when (myScore) {
is MyScore.FAIL_A_GRADE -> "分数差"
is MyScore.PASS_THE_MARK -> "分数及格"
is MyScore.SCORE_WELL -> "分数良好"
is MyScore.EXCELLENT_MARKS -> "分数优秀,name=${myScore.name}"
}
}
//todo kotlin语言的密封类学习
fun main() {
println(Teachers(MyScore.FAIL_A_GRADE).show())
println(Teachers(MyScore.EXCELLENT_MARKS("kotlin")).show())
}
输出:
分数差
分数优秀,name=kotlin
四、密封类与设计模式
1.状态模式
状态模式是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为。状态模式将状态封装成独立的类,并将请求委托给当前的状态对象,从而实现状态的切换和行为的变化。
1.1kotlin实现
密封类可以表示一个有限的状态集合,而且可以在when表达式中进行完备的检查,不需要使用else分支。例如,假设有一个电灯类,它有两种状态:开和关,每种状态下可以执行不同的操作。可以用以下的代码来实现:
Kotlin
// 定义一个密封类表示状态
sealed class State {
// 定义一个抽象方法表示操作
abstract fun operate()
}
// 定义一个开状态的子类,继承自State
class On : State() {
// 重写操作方法,打印信息并切换到关状态
override fun operate() {
println("The light is on, turn it off.")
Light.state = Off()
}
}
// 定义一个关状态的子类,继承自State
class Off : State() {
// 重写操作方法,打印信息并切换到开状态
override fun operate() {
println("The light is off, turn it on.")
Light.state = On()
}
}
// 定义一个电灯类,它有一个状态属性,初始为关状态
object Light {
var state: State = Off()
// 定义一个方法,根据当前状态执行操作
fun switch() {
state.operate()
}
}
// 测试代码
fun main() {
// 创建一个电灯对象
val light = Light
// 调用switch方法,根据当前状态执行操作
light.switch() // The light is off, turn it on.
light.switch() // The light is on, turn it off.
light.switch() // The light is off, turn it on.
}
1.2java实现
在java中,实现状态模式的一种方式是使用枚举类,因为枚举类也可以表示一个有限的状态集合,而且可以实现接口或抽象类,从而定义不同的行为。例如,用java实现上面的例子,可以用以下的代码:
Kotlin
// 定义一个接口表示状态
interface State {
// 定义一个抽象方法表示操作
void operate();
}
// 定义一个枚举类表示状态,实现State接口
enum LightState implements State {
// 定义两个枚举常量,分别表示开和关状态
// 在每个枚举常量的构造器中,传入一个State对象,表示该状态下的行为
ON(new State() {
// 重写操作方法,打印信息并切换到关状态
@Override
public void operate() {
System.out.println("The light is on, turn it off.");
Light.state = OFF;
}
}),
OFF(new State() {
// 重写操作方法,打印信息并切换到开状态
@Override
public void operate() {
System.out.println("The light is off, turn it on.");
Light.state = ON;
}
});
// 定义一个私有的State属性,表示该枚举常量对应的状态对象
private final State state;
// 定义一个私有的构造器,接收一个State对象作为参数,赋值给state属性
private LightState(State state) {
this.state = state;
}
// 定义一个公共的方法,调用state属性的operate方法
public void operate() {
state.operate();
}
}
// 定义一个电灯类,它有一个状态属性,初始为关状态
class Light {
// 定义一个静态的LightState属性,表示电灯的状态,初始为OFF
public static LightState state = LightState.OFF;
// 定义一个方法,根据当前状态执行操作
public void switch() {
state.operate();
}
}
// 测试代码
public class Main {
public static void main(String[] args) {
// 创建一个电灯对象
Light light = new Light();
// 调用switch方法,根据当前状态执行操作
light.switch(); // The light is off, turn it on.
light.switch(); // The light is on, turn it off.
light.switch(); // The light is off, turn it on.
}
}
可以看出,使用kotlin的密封类实现状态模式的优点是:
- 代码更简洁,不需要定义接口或抽象类,也不需要使用匿名内部类
- 代码更安全,不需要担心在其他地方出现未知的状态,也不需要使用else分支处理默认情况
- 代码更易读,可以清楚地看出状态之间的层次关系和转换逻辑
使用java的枚举类实现状态模式的优点是:
- 代码更统一,不需要在不同的类中定义状态,也不需要使用对象来表示状态
- 代码更高效,不需要创建多个状态对象,也不需要使用多态来调用操作方法
2. 访问者模式
访问者模式是一种行为型设计模式,它允许在不修改已有类的结构的情况下,定义作用于这些类的新操作。访问者模式将元素的结构和元素的操作分离,使得操作可以根据不同的元素类型而变化。
2.1kotlin实现
在kotlin中,可以使用密封类来实现访问者模式,因为密封类可以表示一个有限的元素集合,而且可以在when表达式中进行完备的检查,不需要使用else分支。例如,假设有一个表达式类,它有两种子类:数字和加法,每种子类都可以被访问者访问,执行不同的操作。可以用以下的代码来实现:
Kotlin
// 定义一个密封类表示表达式
sealed class Expr {
// 定义一个抽象方法表示接受访问者的访问
abstract fun <R> accept(visitor: Visitor<R>): R
}
// 定义一个数字的子类,继承Expr类,有一个value属性表示数字的值
data class Num(val value: Int) : Expr() {
// 重写accept方法,调用访问者的visitNum方法,传入自己作为参数
override fun <R> accept(visitor: Visitor<R>): R {
return visitor.visitNum(this)
}
}
// 定义一个加法的子类,继承Expr类,有两个Expr属性表示左右操作数
data class Sum(val left: Expr, val right: Expr) : Expr() {
// 重写accept方法,调用访问者的visitSum方法,传入自己作为参数
override fun <R> accept(visitor: Visitor<R>): R {
return visitor.visitSum(this)
}
}
// 定义一个访问者接口,泛型参数R表示访问的结果类型
interface Visitor<R> {
// 定义一个访问数字的方法,接收一个Num对象作为参数,返回一个R类型的结果
fun visitNum(num: Num): R
// 定义一个访问加法的方法,接收一个Sum对象作为参数,返回一个R类型的结果
fun visitSum(sum: Sum): R
}
// 定义一个求值的访问者类,实现Visitor接口,泛型参数为Int
class EvalVisitor : Visitor<Int> {
// 重写访问数字的方法,返回数字的值
override fun visitNum(num: Num): Int {
return num.value
}
// 重写访问加法的方法,返回左右操作数的求值结果的和
override fun visitSum(sum: Sum): Int {
return sum.left.accept(this) + sum.right.accept(this)
}
}
// 定义一个打印的访问者类,实现Visitor接口,泛型参数为String
class PrintVisitor : Visitor<String> {
// 重写访问数字的方法,返回数字的字符串表示
override fun visitNum(num: Num): String {
return num.value.toString()
}
// 重写访问加法的方法,返回加法的字符串表示,用括号括起来
override fun visitSum(sum: Sum): String {
return "(${sum.left.accept(this)} + ${sum.right.accept(this)})"
}
}
// 测试代码
fun main() {
// 创建一个表达式对象,表示1 + (2 + 3)
val expr = Sum(Num(1), Sum(Num(2), Num(3)))
// 创建一个求值的访问者对象
val evalVisitor = EvalVisitor()
// 创建一个打印的访问者对象
val printVisitor = PrintVisitor()
// 调用表达式的accept方法,传入求值的访问者,打印求值的结果
println(expr.accept(evalVisitor)) // 6
// 调用表达式的accept方法,传入打印的访问者,打印表达式的字符串表示
println(expr.accept(printVisitor)) // (1 + (2 + 3))
}
可以看出,使用kotlin的密封类实现访问者模式的优点是:
- 代码更简洁,不需要定义抽象方法或接口,也不需要使用类型转换或类型检查
- 代码更安全,不需要担心在其他地方出现未知的表达式类型,也不需要使用else分支处理默认情况
- 代码更易读,可以清楚地看出表达式之间的层次关系和访问者的操作逻辑
2.2 java实现
Kotlin
// 抽象元素
interface Animal {
void accept(Visitor visitor); // 接受一个抽象访问者
}
// 具体元素Lion
class Lion implements Animal {
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // 调用具体访问者对自己进行操作
}
public String roar() {
return "Roar!";
}
}
// 具体元素Tiger
class Tiger implements Animal {
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // 调用具体访问者对自己进行操作
}
public String growl() {
return "Growl!";
}
}
// 具体元素Elephant
class Elephant implements Animal {
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // 调用具体访问者对自己进行操作
}
public String trumpet() {
return "Trumpet!";
}
}
// 抽象访问者
interface Visitor {
void visit(Lion lion); // 访问具体元素Lion
void visit(Tiger tiger); // 访问具体元素Tiger
void visit(Elephant elephant); // 访问具体元素Elephant
}
// 具体访问者Feed
class Feed implements Visitor {
@Override
public void visit(Lion lion) {
System.out.println("Feed the lion with meat.");
}
@Override
public void visit(Tiger tiger) {
System.out.println("Feed the tiger with chicken.");
}
@Override
public void visit(Elephant elephant) {
System.out.println("Feed the elephant with banana.");
}
}
// 具体访问者Observe
class Observe implements Visitor {
@Override
public void visit(Lion lion) {
System.out.println("Observe the lion: " + lion.roar());
}
@Override
public void visit(Tiger tiger) {
System.out.println("Observe the tiger: " + tiger.growl());
}
@Override
public void visit(Elephant elephant) {
System.out.println("Observe the elephant: " + elephant.trumpet());
}
}
// 具体访问者Train
class Train implements Visitor {
@Override
public void visit(Lion lion) {
System.out.println("Train the lion to jump through a hoop.");
}
@Override
public void visit(Tiger tiger) {
System.out.println("Train the tiger to stand on a ball.");
}
@Override
public void visit(Elephant elephant) {
System.out.println("Train the elephant to sit on a stool.");
}
}
// 对象结构类Zoo
class Zoo {
private List<Animal> animals = new ArrayList<>();
// 添加一个新动物
public void add(Animal animal) {
animals.add(animal);
}
// 移除一个已有动物
public void remove(Animal animal) {
animals.remove(animal);
}
// 接受一个抽象访问者,并将所有动物传递给它进行处理
public void accept(Visitor visitor) {
for (Animal animal : animals) {
animal.accept(visitor);
}
}
}
测试代码:
Kotlin
public class Test {
public static void main(String[] args) {
Zoo zoo = new Zoo();
zoo.add(new Lion());
zoo.add(new Tiger());
zoo.add(new Elephant());
Visitor feed = new Feed();
Visitor observe = new Observe();
Visitor train = new Train();
zoo.accept(feed);
zoo.accept(observe);
zoo.accept(train);
}
}
运行测试代码并显示输出结果:
Kotlin
Feed the lion with meat.
Feed the tiger with chicken.
Feed the elephant with banana.
Observe the lion: Roar!
Observe the tiger: Growl!
Observe the elephant: Trumpet!
Train the lion to jump through a hoop.
Train the tiger to stand on a ball.
Train the elephant to sit on a stool.
五、与final类对比
特性 | 密封类 | final类 |
---|---|---|
可继承性 | 部分可继承,只能被指定的类继承 | 不可继承 |
受保护成员或虚成员 | 不允许,因为密封类的子类必须是密封类或final类 | 允许,因为final类没有子类 |
抽象性 | 允许,因为密封类可以是抽象的或具体的 | 不允许,因为抽象类必须被继承 |
相似之处 | 都是限制类的继承,都不能声明为抽象的,都可以继承别的类或接口 | 都是限制类的继承,都不能声明为抽象的,都可以继承别的类或接口 |
不同之处 | 可以指定哪些类可以作为其子类,可以实现多态性,可以用于一些特定的设计模式 | 不能有任何子类,不能实现多态性,不能用于一些特定的设计模式 |
优点 | 可以保证封装性和多态性,可以提高代码的可读性和可维护性,可以避免不必要的继承或实现 | 可以保证封装性和不变性,可以提高代码的执行效率,可以避免类的滥用 |
缺点 | 可能增加代码的复杂度和冗余,可能限制类的扩展性和灵活性,可能与一些框架或库不兼容 | 可能增加代码的耦合度和僵化度,可能限制类的扩展性和灵活性,可能与一些框架或库不兼容 |
应用场景 | 可以用于实现一些特定的设计模式,例如状态模式、策略模式、访问者模式等,这些模式需要明确地定义一组有限的子类或实现类 | 可以用于实现一些不需要继承的类,例如工具类、常量类、单例类等,这些类需要保证其不变性和唯一性 |
六、应用
如果说,我们一个需求,控制档位,有两种模式,一种是inch 一种是mm。怎么表达?
我们想到是两个数组一一对应,如果java写就是枚举。
java
public class Test {
public static void main(String[] args) {
System.out.println("根据英文描述获取中文描述:" + ColorEnum.getDescriptionByCode(ColorEnum.YELLOW.getCode()));
System.out.println("根据输入字符获取中文描述(有):" + ColorEnum.getDescriptionByCode("RED"));
System.out.println("根据输入字符获取中文描述(无):" + ColorEnum.getDescriptionByCode("PURPLE"));
}
}
enum ColorEnum {
YELLOW("YELLOW", "黄色"),
WHITE("WHITE", "白色"),
GREEN("GREEN", "绿色"),
BLUE("BLUE", "蓝色"),
RED("RED", "红色");
//枚举标识码(中文描述)
private final String description;
//这个必须定义,且成员变量的类型及个数必须对应于上边枚举的定义
//枚举标识码(英文描述)
private final String code;
ColorEnum(String code, String description) {
this.code = code;
this.description = description;
}
public String getCode() {
return code;
}
public String getDescription() {
return description;
}
public static String getDescriptionByCode(String code) {
for (ColorEnum value : ColorEnum.values()) {
if (value.getCode().equals(code)) {
return value.getDescription();
}
}
return null;
}
}
执行结果:
根据英文描述获取中文描述:黄色
根据输入字符获取中文描述(有):红色
根据输入字符获取中文描述(无):null
那么kotlin怎么写:
用data数据类+密封类
Kotlin
sealed class OrderStatus {
abstract val orderId: String
data class Pending(override val orderId: String) : OrderStatus()
data class Shipped(override val orderId: String, val trackingNumber: String) : OrderStatus()
data class Delivered(override val orderId: String, val deliveryDate: String) : OrderStatus()
object Cancelled : OrderStatus()
fun getDescription(): String {
return when (this) {
is Pending -> "Order $orderId is pending"
is Shipped -> "Order $orderId has been shipped with tracking number $trackingNumber"
is Delivered -> "Order $orderId has been delivered on $deliveryDate"
Cancelled -> "Order $orderId has been cancelled"
}
}
}
fun main() {
val pendingOrder = OrderStatus.Pending("12345")
val shippedOrder = OrderStatus.Shipped("67890", "XYZ123")
val deliveredOrder = OrderStatus.Delivered("23456", "2023-09-25")
val cancelledOrder = OrderStatus.Cancelled
println(pendingOrder.getDescription())
println(shippedOrder.getDescription())
println(deliveredOrder.getDescription())
println(cancelledOrder.getDescription())
}