일반화 처리 - 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 { }