重构 - Deal with Generalization

这一章是关于重构概括关系的总结。概括关系,简而言之就是一种代码复用体系,也就是面向对象的类系统的重构。常见的概括关系重构包括字段和函数的上移和下移、构造函数的上移、使用工厂方法代替构复杂造器。除了处理一个现有的类型系统,还可以对类型系统进行更改,比如将父类拆分为父类和子类,将相关但是不同的类移动到父类后直接删除、合并抽象逻辑为父类、从函数中提取公共模板方法、定义两个类的角色 —— 接口。此外,本文还探讨了概括关系的两种选择:委托和继承,前者适用于 has 关系,后者适用于 is 关系。

现有继承体系重构

Pull Up Field

当两个子类拥有相同的字段,将该字段移至超类。

package BEFORE {
  class People {
    var name:String = ""
    override def toString: String = s"${getClass.getSimpleName} - $name"
  }
  class Student extends People {
    var age:Int = 0
  }
  class Employee extends People {
    var age:Int = 20
  }
  object Test extends App {
    val a = new People
    a.name = "Corkine"
    val b = new Student
    b.name = "Corkine"
    b.age = 40
    val c = new Employee
    c.name = "Corkine"
    c.age = 30
    println(a,b,c)
  }
}
package AFTER {
  class People {
    var name:String = ""
    var age:Int = 0
    override def toString: String = s"${getClass.getSimpleName} - $name"
  }
  class Student extends People {
    age = 0
  }
  class Employee extends People {
    age = 20
  }
  object Test extends App {
    val a = new People
    a.name = "Corkine"
    val b = new Student
    b.name = "Corkine"
    b.age = 40
    val c = new Employee
    c.name = "Corkine"
    c.age = 30
    println(a,b,c)
  }
}

Pull Up Method

有些函数,在各个子类中产生完全相同的结果,将其移动到超类。

package Before {
  class People()
  class Student(name:String) extends People {
    def sayHi:String = "Hello From Student"
  }
  class Employee(name:String) extends People {
    def sayHi:String = "Hello From Employee"
  }
  object Test extends App {
    val a = new People
    val b = new Student("Stu")
    val c = new Employee("Emp")
    println(a,b.sayHi,c.sayHi)
  }
}
package After {
  class People(name:String) {
    def sayHi:String = s"Hello From ${getClass.getSimpleName}"
  }
  class Student(name:String) extends People(name)
  class Employee(name:String) extends People(name)
  object Test extends App {
    val a = new People("Corkine")
    val b = new Student("Corkine")
    val c = new Employee("Corkine")
    println(a.sayHi, b.sayHi, c.sayHi)
  }
}

Pull Up Extract Method

在多个子类中的有些函数,其大部分逻辑相同,将这个函数重构,拆分,将共同部分合并到超类。

package Before {
  class People {
    var name = ""
    var age = 0
  }
  class Student extends People {
    var school:String = ""
    def getInformation: String = {
      s"Student - $name, $age, $school"
    }
  }
  class Employee extends People {
    var company:String = ""
    def getInformation: String = {
      s"Employee - $name, $age, $company"
    }
  }
  object Test extends App {
    val a = new People
    a.name = "Corkine"
    val b = new Student
    b.name = "Corkine"
    b.age = 22
    b.school = "CCNU"
    val c = new Employee
    c.name = "Corkine"
    c.age = 22
    c.company = "CCCP"
    println(a, b.getInformation, c.getInformation)
  }
}
package After {
  class People {
    var name = ""
    var age = 0
    def getBasicInformation:String = getClass.getSimpleName + s" - $name, $age, "
  }
  class Student extends People {
    var school:String = ""
    def getInformation: String = {
      getBasicInformation + s"$school"
    }
  }
  class Employee extends People {
    var company:String = ""
    def getInformation: String = {
      getBasicInformation + s"$company"
    }
  }
  object Test extends App {
    val a = new People
    a.name = "Corkine"
    val b = new Student
    b.name = "Corkine"
    b.age = 22
    b.school = "CCNU"
    val c = new Employee
    c.name = "Corkine"
    c.age = 22
    c.company = "CCCP"
    println(a, b.getInformation, c.getInformation)
  }
}

Pull Up Constructor Body

在每个子类中拥有一些构造函数,可以将其移动到超类,然后在子类调用它。

package Before {
  class Person(val name:String, val age:Int)
  class Student(name:String, age:Int, school:String) extends Person(name,age) {
    println("Init Main Constructor ...")
  }
  class Employee(name:String, age:Int, val salary:Int) extends Person(name,age) {
    println("Init Main Constructor ...")
  }
  object Test extends App {
    val a = new Person("Corkine",22)
    val b = new Student("Student", 22, "CCNU")
    val c = new Employee("Employee",22,1000)
    println(a,b,c)
  }
}
package After {
  class Person(val name:String, val age:Int) {
    println("Init Main Constructor ...")
  }
  class Student(name:String, age:Int, school:String) extends Person(name,age)
  class Employee(name:String, age:Int, val salary:Int) extends Person(name,age)
  object Test extends App {
    val a = new Person("Corkine",22)
    val b = new Student("Student", 22, "CCNU")
    val c = new Employee("Employee",22,1000)
    println(a,b,c)
  }
}

Push Down Field

当超类中某个字段只被部分,而非全部子类用到,将这个字段移动到它的那些子类中去。

package Before {
  class Person(val name:String, val age:Int) {
    val schoolCode: Int = {
      this match {
        case s:Student => s.school.length
        case _ => -1
      }
    }
  }
  class Student(name:String, age:Int, val school:String) extends Person(name,age) 
  class Employee(name:String, age:Int, val salary:Int) extends Person(name,age)
  object Test extends App {
    val a = new Person("Corkine",22)
    val b = new Student("Student", 22, "CCNU")
    val c = new Employee("Employee",22,1000)
    println(a.schoolCode,b.schoolCode,c.schoolCode)
  }
}
package After {
  class Person(val name:String, val age:Int)
  class Student(name:String, age:Int, val school:String) 
        extends Person(name,age) {
    val schoolCode: Int = this.school.length
  }
  class Employee(name:String, age:Int, val salary:Int) 
        extends Person(name,age)
  object Test extends App {
    val a = new Person("Corkine",22)
    val b = new Student("Student", 22, "CCNU")
    val c = new Employee("Employee",22,1000)
    println(a,b.schoolCode,c)
  }
}

Push Down Method

当超类中某个函数只和部分,而非全部子类有关,将这个函数移动到相关的子类中去。

package Before {
  class Person(val name:String, val age:Int) {
    def schoolCode: Int = {
      this match {
        case s:Student => s.school.length
        case _ => -1
      }
    }
  }
  class Student(name:String, age:Int, val school:String) extends Person(name,age)
  class Employee(name:String, age:Int, val salary:Int) extends Person(name,age)
  object Test extends App {
    val a = new Person("Corkine",22)
    val b = new Student("Student", 22, "CCNU")
    val c = new Employee("Employee",22,1000)
    println(a.schoolCode,b.schoolCode,c.schoolCode)
  }
}
package After {
  class Person(val name:String, val age:Int)
  class Student(name:String, age:Int, val school:String) 
        extends Person(name,age) {
    def schoolCode: Int = this.school.length
  }
  class Employee(name:String, age:Int, val salary:Int) 
        extends Person(name,age)
  object Test extends App {
    val a = new Person("Corkine",22)
    val b = new Student("Student", 22, "CCNU")
    val c = new Employee("Employee",22,1000)
    println(a,b.schoolCode,c)
  }
}

重构现有继承结构

拆分:Extract Subclass

当类中某些特性值被某些,而非全部实例用到,新建一个子类,然后将上面所说的特性移动到子类中去。

同 Pull Up Method,不过这里新建了一个子类,略。

拆分:Extract Hierarchy

当一个类做了太多的事情,考虑将其分解,然后将一部分任务交给子类去做。这和 Pull Up Method、Field、Extract Subclass 的思想相同,不过,Extract Subclass 更多的是将大的类的功能拆分,而这里的 Extract Hierarchy 考虑的更多是,用一个子类代表一种类的特殊情况(当这个原本的类使用大量的条件表达式时,这样的拆分更有效)。

合并:Extract Superclass

当两个类有