ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Refactoring] 7장 : 캡슐화
    책/Refactoring 2판 2024. 2. 14. 11:31
    반응형

    레코드 캡슐화하기

    // before
    someRecord = {name: "choijaewon", country: "korea"};
    
    // after
    class SomeRecord {
        constructor (data) {
            this._name = data.name;
            this._country = data.country;
        }
    
        get name() {
            return this._name;
        }
    
        set name(arg) {
            this._name = arg;
        }
    
        get country() {
            return this._country;
        }
    
        set country(arg) {
            this._country = arg;
        }
    }
    • 레코드를 직접 노출하는 것이 아닌 클래스로 감싸고, 접근자 메서드를 통해 접근 및 수정할 수 있도록 바꾼다.

    컬렉션 캡슐화하기

    // before
    class SomeClass {
        get someList() {
            return this.someList;
        }
    
        set someList(aList) {
            this.someList = aList;
        }
    }
    
    // after
    class SomeClass {
        get someList() {
            return this.someList.slice();
        }
    
        addSome();
    
        removeSome();
    }
    • 이전 코드는 get을 호출할 때 필드의 컬렉션을 그대로 반환한다.
    • 이러면 get() 으로 받아온 뒤 그 컬렉션을 수정하면 그대로 반영된다 → 캡슐화가 깨짐
    • 그래서 컬렉션을 반환할 때는 읽기 전용 프록시나 복제본을 반환하게 한다.
    • 또한 전체를 바꾸는 setter는 없애고, 원소를 추가/제거하는 메소드를 만든다.
      • 만약 없앨 수 없다면, setter로 들어온 컬렉션의 복제본을 필드로 저장한다 → 세터로 설정한 컬렉션을 외부에서 수정하면 내부에 반영되므로

    기본형을 객체로 바꾸기

    // before
    const phoneNumber = "010-4321-4321";
    
    if (phoneNumber == "010-1234-5678") {
        // do something
    }
    
    // after
    const phoneNumber = new PhoneNumber("010-4321-4321");
    const anotherNumber = new PhoneNumber("010-1234-5678");
    
    if (phoneNumber.isEqual(anotherNumber)) {
        // do something
    }
    • 무언가 의미가 있는 데이터를 기본형으로만 표현하는 경우 이를 클래스로 만들어 재사용성과 가독성을 높인다.

    임시 변수를 질의 함수로 바꾸기

    // before
    const someResult = a * b;
    
    if (someResult > 1000) {
        // do something
    }
    
    // after
    function someResult(a, b) {
        return a * b;
    }
    
    if (someResult(a, b) > 1000) {
        // do something
    }
    
    // after 2
    
    class Some {
        a;
        b;
    
        get someResult() {return a * b}
    
        someFunc() {
            if (someResult() > 1000) {
                // do something
            }
        }
    }
    • 반복적으로 사용되는 연산 값을 저장하는 변수가 꼭 있다.
    • 이런 걸 함수로 추출한 뒤, 이 함수를 호출하는 형태로 바꾸는 것
    • 이 방식은 특히 클래스에서 매우 유용하다. → 필드를 통해 매개변수가 필요없기 때문이다.

    클래스 추출하기

    // before
    class Person {
        get countryCode() {
            return this.countryCode;
        }
    
        get officeNumber() {
            return this.officeNumber;
        }
    }
    
    // after
    class Person {
        get countryCode() {
            return this.countryCode;
        }
    
        get officeNumber() {
            return this.telephoneNumber.number;
        }
    }
    
    class TelephoneNumber {
        get areaCode() {
            return this.areaCode;
        }
    
        get number() {
            return this.number;
        }
    }
    • 개발이 진행되면서 한 클래스가 너무 많은 역할을 담당하는 경우가 있다.
    • 이 경우, 각 역할을 클래스로 추출하고, 원래 클래스가 추출된 클래스의 객체를 필드로 가지도록 리팩터링한다.

    클래스 인라인하기

    // before
    class Person {
        get countryCode() {
            return this.countryCode;
        }
    
        get officeNumber() {
            return this.telephoneNumber.number;
        }
    }
    
    class TelephoneNumber {
        get areaCode() {
            return this.areaCode;
        }
    
        get number() {
            return this.number;
        }
    }
    
    // after
    class Person {
        get countryCode() {
            return this.countryCode;
        }
    
        get officeNumber() {
            return this.officeNumber;
        }
    }
    • 위와 반대로, 어떤 클래스의 역할이 너무 없는 경우도 있다.
    • 이 경우에는 해당 클래스를 사용하는 다른 클래스와 합쳐버릴 수 있다.
    • 옮길 때 먼저 없어질 클래스의 메서드를 합칠 클래스에 모두 만들어주고, 없어질 클래스의 메소드를 호출하는 부분을 하나씩 바꿔준다.
      • 모두 바뀌면 그 때 클래스를 삭제한다.

    위임 숨기기

    // before
    manager = aPerson.department.manager;
    
    // after
    manager = aPerson.manager;
    
    class Person {
        get manager() {
            return this.department.manager;
        }
    }
    • 서버의 필드가 가리키는 객체의 메서드를 호출하려면 클라이언트가 이 위임 객체를 알아야 한다.
    • 또한 어쩌다가 그 객체의 인터페이스가 바뀌면, 이 또한 전부 바뀌어야 한다.
    • 이러한 경우, 이 필드 객체의 필드를 서버에서 호출해주도록 바꾸면 인터페이스가 수정되어도 서버만 바꿔주면 되어서 편하다.

    중개자 제거하기

    // before
    manager = aPerson.manager;
    
    class Person {
        get manager() {
            return this.department.manager;
        }
    }
    
    // after
    manager = aPerson.department.manager;
    • 위와 반대로, 너무 과해지면 해당 클래스가 중개자 역할만 하는 메서드로만 가득 찰 수도 있다.
    • 이 경우, 정확히 반대로 수행해주면 된다.

    알고리즘 교체하기

    // before
    function findPerson(people) {
        for (let i = 0; i < people.length; i++) {
            if (people[i] == "jaewon") {
                return "jaewon";
            }
        }
        return "";
    }
    
    // after
    function findPerson(people) {
        const candidates = ["jaewon"];
        return people.find(p => candidates.includes(p)) || '';
    }
    • 이 방법은 단순히 더 나은 알고리즘을 발견했을 때 그냥 바꿔주는 것이다.
    • 그러나 절차에서 주목할 점이 있다.
      • 교체할 코드를 함수 하나에 모은다
      • 이 함수를 테스트할 코드를 마련한다.
    • 알고리즘 교체 시 그 알고리즘을 함수 하나에 모으고 테스트를 그 함수로 국한시키는 것이 주목할 점인 것 같다.

     

    https://product.kyobobook.co.kr/detail/S000001810241

    반응형
Designed by Tistory.