Android Kotlin

密封类sealed class

2023-03-11  本文已影响0人  长点点

一、什么是kotlin密封类?

密封类是一种特殊的类,它用来表示受限的类继承结构,即一个类只能有有限的几种子类,而不能有任何其他类型的子类。

密封类使用sealed关键字声明,在Kotlin 1.0中,密封类的所有子类必须嵌套在密封类内部;在Kotlin 1.1中,这个限制放宽了,允许将子类定义在同一个文件中;在Kotlin 1.5中,这个限制进一步放宽了,允许将子类定义在任何地方,只要保证子类可见性不高于父类。

密封类最大的优点是可以配合when表达式实现完备性检查(exhaustive check),即编译器可以检测出是否覆盖了所有可能的分支情况,如果没有则会报错或提示警告。这样可以避免遗漏某些情况导致逻辑错误或运行时异常。

例如,我们可以定义一个表示运算符的密封类,以及它的四个子类:

//定义一个密封类
sealed class Operator

//定义四个子类,分别表示加、减、乘、除
class Add : Operator()
class Subtract : Operator()
class Multiply : Operator()
class Divide : Operator()

二、kotlin密封类的优缺点

2.1 优点

2.2 缺点

三、kotlin密封类的使用方式

密封类适合用于表示有限的几种类型的情况,例如:

下面我们用一个简单的示例来演示如何使用密封类。我们定义一个表示计算器的密封类,它有两个子类,分别表示数字和运算符:

//定义一个表示计算器的密封类
sealed class Calculator

//定义两个子类,分别表示数字和运算符
data class Number(val value: Int) : Calculator()
data class Operator(val op: String) : Calculator()

然后我们定义一个函数,用来根据输入的密封类对象,计算结果:


//定义一个函数,用来根据输入的密封类对象,计算结果
fun calculate(a: Calculator, b: Calculator): Int {
    //使用when表达式,根据不同的子类类型,进行不同的操作
    return when (a) {
        //如果a是数字,再判断b的类型
        is Number -> when (b) {
            //如果b也是数字,就根据a的值和b的值进行运算
            is Number -> when (a.value) {
                //如果a的值是0,就返回0
                0 -> 0
                //如果a的值是1,就返回b的值
                1 -> b.value
                //否则,就根据b的运算符进行运算
                else -> when (b.op) {
                    //如果b的运算符是加号,就返回a的值加b的值
                    "+" -> a.value + b.value
                    //如果b的运算符是减号,就返回a的值减b的值
                    "-" -> a.value - b.value
                    //如果b的运算符是乘号,就返回a的值乘b的值
                    "*" -> a.value * b.value
                    //如果b的运算符是除号,就返回a的值除b的值
                    "/" -> a.value / b.value
                    //其他情况,就抛出异常
                    else -> throw IllegalArgumentException("Invalid operator: ${b.op}")
                }
            }
            //如果b是运算符,就抛出异常
            is Operator -> throw IllegalArgumentException("Cannot calculate two operators")
        }
        //如果a是运算符,就抛出异常
        is Operator -> throw IllegalArgumentException("Cannot calculate an operator and a number")
    }
}

四、密封类与设计模式

1.状态模式

状态模式是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为。状态模式将状态封装成独立的类,并将请求委托给当前的状态对象,从而实现状态的切换和行为的变化。

1.1kotlin实现

密封类可以表示一个有限的状态集合,而且可以在when表达式中进行完备的检查,不需要使用else分支。例如,假设有一个电灯类,它有两种状态:开和关,每种状态下可以执行不同的操作。可以用以下的代码来实现:

// 定义一个密封类表示状态
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实现上面的例子,可以用以下的代码:

// 定义一个接口表示状态
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的密封类实现状态模式的优点是:

使用java的枚举类实现状态模式的优点是:

2. 访问者模式

访问者模式是一种行为型设计模式,它允许在不修改已有类的结构的情况下,定义作用于这些类的新操作。访问者模式将元素的结构和元素的操作分离,使得操作可以根据不同的元素类型而变化。

2.1kotlin实现

在kotlin中,可以使用密封类来实现访问者模式,因为密封类可以表示一个有限的元素集合,而且可以在when表达式中进行完备的检查,不需要使用else分支。例如,假设有一个表达式类,它有两种子类:数字和加法,每种子类都可以被访问者访问,执行不同的操作。可以用以下的代码来实现:

// 定义一个密封类表示表达式
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的密封类实现访问者模式的优点是:

2.2 java实现
// 抽象元素
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);    
     }    
}   
}

测试代码:

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);
   }
}

运行测试代码并显示输出结果:

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类没有子类
抽象性 允许,因为密封类可以是抽象的或具体的 不允许,因为抽象类必须被继承
相似之处 都是限制类的继承,都不能声明为抽象的,都可以继承别的类或接口 都是限制类的继承,都不能声明为抽象的,都可以继承别的类或接口
不同之处 可以指定哪些类可以作为其子类,可以实现多态性,可以用于一些特定的设计模式 不能有任何子类,不能实现多态性,不能用于一些特定的设计模式
优点 可以保证封装性和多态性,可以提高代码的可读性和可维护性,可以避免不必要的继承或实现 可以保证封装性和不变性,可以提高代码的执行效率,可以避免类的滥用
缺点 可能增加代码的复杂度和冗余,可能限制类的扩展性和灵活性,可能与一些框架或库不兼容 可能增加代码的耦合度和僵化度,可能限制类的扩展性和灵活性,可能与一些框架或库不兼容
应用场景 可以用于实现一些特定的设计模式,例如状态模式、策略模式、访问者模式等,这些模式需要明确地定义一组有限的子类或实现类 可以用于实现一些不需要继承的类,例如工具类、常量类、单例类等,这些类需要保证其不变性和唯一性
上一篇 下一篇

猜你喜欢

热点阅读