일반화 처리 - ChoDragon9/posts GitHub Wiki

일반화 처리

일반화는 주로 상속 계층구조나 상속 계층의 위나 아래로, 즉 상위클래스나 하위클래스로 메서드를 옮기는 기법이다.

필드 상향(Pull Up Field)

두 하위 클래스에 같은 필드가 들어 있을 땐 필드를 상위클래스로 옮기자.

여러 하위클래스에 중복된 기능이 들어 있는 경우가 많다. 특히 특정한 몇 개의 필드가 중복될 수 있다. 상위클래스로 옮기게 되면 데이터 선언의 중복이 없어지고 그 필드를 사용하는 기능이 하위클래스에서 상위클래스로 옮겨진다.

// Before
class Employee { }
class Salesman { 
  constructor (name) {
    this.name = name
  }
}
class Engineer { 
  constructor (name) {
    this.name = name
  }
}

// After
class Employee {
  constructor (name) {
    this.name = name
  }
}
class Salesman { }
class Engineer { }

메서드 상향(Pull Up Method)

기능이 같은 메서드가 여러 하위클래스에 들어 있을 땐 그 메서드를 상위클래스로 옮기자

class Employee { }
class Salesman { 
  setName (name) {
    this.name = name
  }
}
class Engineer { 
  setName (name) {
    this.name = name
  }
}

// After
class Employee {
  setName (name) {
    this.name = name
  }
}
class Salesman { }
class Engineer { }

생성자 내용 상향(Pull Up Constructor Body)

하위클래스마다 거의 비슷한 내용의 생성자가 있을 땐 상위클래스에 생성자를 작성하고, 그 생성자를 하위 클래스의 메서드에서 호출하자.

// Before
class Employee { }
class Manager extends Employee {
  constructor (name, id, grade) {
    super()
    this.name = name
    this.id = id
    this.grade = grade
  }
}

// After
class Employee {
  constructor (name, id) {
    this.name = name
    this.id = id
  }
}
class Manager extends Employee {
  constructor (name, id, grade) {
    super(name, id)
    this.grade = grade
  }
}

메서드 하향(Push Down Method)

상위클래스에 있는 기능을 일부 하위클래스만 사용할 땐 그 기능을 관련된 하위클래스 안으로 옮기자

// Before
class Employee {
  getQuota () { }
}
class Salesman extends Employee {
  getData () {
    return super.getQuata()
  } 
}
class Engineer extends Employee {
  getData () {
    return 'Something'
  } 
}

// After
class Employee { }
class Salesman extends Employee {
  getQuota () { }
  getData () {
    return this.getQuata()
  } 
}
class Engineer extends Employee {
  getData () {
    return 'Something'
  } 
}

필드 하향(Pull Down Field)

일부 하위클래스만이 사용하는 필드가 있을 땐 그 필드를 사용하는 하위클래스로 옮기자.

// Before
class Employee {
  constructor () {
    this.quota = 1000
  }
}
class Salesman extends Employee {
  getData () {
    return super.quota
  } 
}
class Engineer extends Employee {
  getData () {
    return 'Something'
  } 
}

// After
class Employee { }
class Salesman extends Employee {
  constructor () {
    super()
    this.quota = 1000
  }
  getData () {
    return this.quota
  } 
}
class Engineer extends Employee {
  getData () {
    return 'Something'
  } 
}

하위클래스 추출(Extract Subclass)

일부 인스턴스에만 사용되는 기능이 든 클래스가 있을 땐 그 기능 부분을 전담하는 하위클래스를 작성하자.

// Before
class JobItem {
  getTotalPrice () { }
  getUnitPrice () { }
  getEmployee () { }
}

// After
class JobItem {
  getTotalPrice () { }
  getUnitPrice () { }
}

class LaborItem extends JobItem {
  getUnitPrice () { super.getUnitPrice() }
  getEmployee () { }
}

상위클래스 추출(Extract Superclass)

기능이 비슷한 두 클래스가 있을 땐 상위클래스를 작성하고 공통된 기능들을 그 상위클래스로 옮기자

// Before
class Department {
  getTotalAnnualCost () { }
  getName () { }
  getHeadCount () { }
}

class Employee {
  getAnnualCost () { }
  getName () { }
  getId () { }
}

// After
class Party {
  getAnnualCost () { }
  getName () { }
}

class Department extends Party {
  getAnnualCost () { super.getAnnualCost() }
  getHeadCount () { }
}

class Employee extends Party {
  getAnnualCost () { super.getAnnualCost() }
  getId () { }
}

인터페이스 추출(Extract Interface)

클래스 인터페이스의 값을 부분을 여러 클라이언트가 사용하거나, 두 클래스에 인터페이스의 일부분이 공통으로 들어 있을 땐 공통 부분을 인터페이스로 빼내자.

// Before
class Employee {
  getRate () { }
  hasSpecialSkill () { }
  getName () { }
  getDepartment () { }
}

// After
interface Billable {
  getRate ()
  hasSpecialSkill ()
}

class Employee implements Billable {
  getRate () { }
  hasSpecialSkill () { }
  getName () { }
  getDepartment () { }
}

계층 병합(Collapse Hierarchy)

상위클래스와 하위클래스가 거의 다르지 않을 땐 둘을 합치자.

// Before
class Employee { }
class Salesman extends Employee { }

// After
class Employee { }

템플릿 메서드 형성(Form Template Method)

하위클래스 안의 두 메서드가 거의 비슷한 단계들을 같은 순서로 수행할 땐 그 단계들을 시그니쳐가 같은 두개의 메서드로 만들어서 두 원본 메서드를 같게 만든 후 두 메서드를 상위클래스를 옮기자

// Before
class Site { }
class ResidentialSite extends Site {
  getBillableAmount () {
    const base = this.units * this.rate
    const tax = base * super.TAX_RATE
    return base * tax
  }
}
class LifelineSite extends Site {
  getBillableAmount () {
    const base = this.units * this.rate * 0.5
    const tax = base * super.TAX_RATE * 0.2
    return base * tax
  }
}

// After
class Site {
  constructor () {
    this.TAX_RATE = 1
  }
  getBillableAmount () {
    return getBaseAmount() + getTaxAmount()
  }
  getBaseAmount () { }
  getTaxAmount () { }
}
class ResidentialSite extends Site {
  constructor () {
    super()
    this.units = 1
    this.rate = 2
  }
  getBaseAmount () {
    return this.units * this.rate
  }
  getTaxAmount () {
    return this.getBaseAmount() * this.TAX_RATE
  }
}
class LifelineSite extends Site {
  constructor () {
    super()
    this.units = 1
    this.rate = 2
  }
  getBaseAmount () {
    return this.units * this.rate * 0.5
  }
  getTaxAmount () {
    return this.getBaseAmount() * this.TAX_RATE * 0.2
  }
}

상속을 위임으로 전환(Replace Inheritance with Delegation)

하위클래스가 상위클래스 인터페이스의 일부반 사용할 때나 데이터를 상속받지 않게 해야 할 땐 상위클래스에 필드를 작성하고, 모든 메서드가 그 상위클래스에 위임하게 수정한 후 하위클래스를 없애자

// Before
class Vector {
  isEmpty () { }
}

class Stack extends Vector { }

// After
class Vector {
  isEmpty () { }
}

class Stack {
  constructor () {
    this.vector = new Vector()
  }
  isEmpty () {
    return this.vector.isEmpty()
  }
}

위임을 상속으로 전환(Replace Delegation with Inheritance)

위임을 이용 중인데 인터페이스 전반에 간단한 위임으로 도배하게 될 땐 위임클래스를 대리 객체의 하위클래스로 만들자.

// Before
class Person {
  getName () { }
}
class Employee {
  constructor () {
    this.person = new Person()
  }
  getName () {
    return this.person.getName()
  }
}

// After
class Person {
  getName () { }
}
class Employee extends Person { }