7장 캡슐화
개요
모듈을 분리하는 가장 중요한 기준은 시스템에서 각 모듈이 자신을 제외한 다른 부분에 드러내지 않아야 할 비밀을 얼마나 잘 숨기는데 있습니다.
클래스는 본래 정보를 숨기기 좋게 설계할 수 있습니다.
본 장에서는 클래스에서 본래의 정보를 숨기기 위한 캡슐화 방법에 대해 알아보려 합니다.
레코드 캡슐화하기
- 리팩터링 전
organization = {name: “애크미 구스베리”, country: “GB”}
- 리팩터링 후
class Orgnization:
def __init__(self, data):
self.__name = data.name
self.__country = data.country
def getName(self):
return self.__name
def setName(self, name):
self.__name = name
def getCountry(self):
return self.__country
def setCountry(self, country):
self.__country = country
배경
대부분의 프로그래밍 언어는 데이터 레코드를 표현하는 구조를 제공합니다. 레코드는 연관된 여러 데이터를 직관적인 방식으로 묶을 수 있어 각각을 취급할 때 보다 훨씬 의미있는 단위로 전달할 수 있게 됩니다.
절차
- 레코드를 담은 변수를 캡슐화한다.
- 레코드를 감싼 단순한 클래스로 해당 변수의 내용을 교체한다.
- 테스트한다.
- 원래 레코드 대신 새로 정의한 한 클래스 타입의 객체를 반환하는 함수들을 새로 만든다.
- 레코드를 반환하는 예전 함수를 사용하는 코드를 4.에서 만든 새 함수를 사용하도록 바꾼다.
- 클래스에서 원본 데이터를 반환하는 접근자와 원본 레코드를 반환하는 함수들을 제거한다.
- 테스트한다.
컬렉션 캡슐화하기
- 리팩터링 전
class Person :
def getCourses(self)
return self.__courses
def setCourses(self, aList):
self.__courses = aList
- 리팩터링 후
class Person :
def getCourses(self)
return self.__courses.slice()
deff addCourse(self, aCourse):
……
def removeCourse(self, aCourse):
……
배경
컬렉션 변수의 접근을 캡슐화하면서 getter가 컬렉션 자체를 반환하도록 한다면, 그 컬렉션을 감싼 클레스가 모르게 컬렉션의 원소들이 변경될 수 있습니다.
이러한 문제를 방지하기 위해 흔히 add() 와 remove()라는 이름의 컬렉션 변경자 메서드를 사용합니다. 이렇게 컬렉션을 소유한 클래스를 통해서만 원소를 변경하도록 하면 프로그램을 개선하면서 컬렉션 변경 방식도 원하는 대로 수정할 수 있습니다.
절차
- 컬렉션 변수를 캡슐화한다.
- 컬렉션에 원소를 추가 / 제거하는 함수를 추가한다.
- 정적 검사를 수행한다.
- 컬렉션을 참조하는 부분을 모두 찾는다. 컬렉션의 변경자를 호출하는 코드가 모두 앞에서 추가 / 제거 함수를 호출하도록 수정한다.
- 컬렉션 getter를 수정해서 원본 내용을 수정할 수 없는 읽기 전용 프락시나 복제본을 반환하게 한다.
- 테스트한다.
클래스 추출하기
- 리팩터링 전
class Person:
getOfficeAreaCode(self):
return self.__officeAreaCode
getOfficeNumber(self):
return self.__officeNumber
- 리팩터링 후
class Person:
getOfficeAreaCode(self):
return self.__telphoneNumber.getAreaCode()
getOfficeNumber(self):
return self.__telphoneNumber.getNumber()
class TelephoneNumber():
getAreaCode(self):
return self.__areaCode
getNumber(self):
return self.__number
배경
메서드와 데이터가 너무 많은 클래스는 이해하기 쉽지 않으니 잘 살펴보고 적절히 분리하는것이 좋습니다. 만약 일부 데이터와 메서드를 따로 묶을 수 있다면 분리하는것이 좋습니다. 제거해도 다른 필드나 메서드들이 논리적으로 문제가 없다면 분리할 수 있다는 뜻이 됩니다.
절차
- 클래스의 역할을 분리할 방법을 정한다
- 분리될 역할을 담당할 클래스를 새로 만든다
- 원래 클래스의 생성자에서 새로운 클래스의 인스턴스를 생성하여 필드에 저장해둔다.
- 분리될 역할에 필요한 필드들을 새 클래스로 옮긴다
- 매서드들도 새 클래스로 옮긴다
- 양쪽 클래스의 인터페이스를 살펴보면서 불필요한 메서드를 제거하고, 이름도 새로운 환경에 맞게 바꾼다
- 새 클래스를 외부로 노출할지 결정한다
클래스 인라인하기
- 리팩터링 전
class Person:
getOfficeAreaCode(self):
return self.__telphoneNumber.getAreaCode()
getOfficeNumber(self):
return self.__telphoneNumber.getNumber()
class TelephoneNumber():
getAreaCode(self):
return self.__areaCode
getNumber(self):
return self.__number
- 리팩터링 후
class Person:
getOfficeAreaCode(self):
return self.__officeAreaCode
getOfficeNumber(self):
return self.__officeNumber
배경
클래스 인라인하기는 클래스 추출하기를 거꾸로 돌리는 리팩터링입니다. 더 이상 제 역할을 못해서 그대로 두면 안되는 클래스를 인라인 하는게 좋습니다. 역할을 옮기는 리팩터링을 하고 난 뒤 특정 클래스에 남는 역할이 거의 없을 때 이러한 현상이 자주 생기는데, 이때 클래스를 인라인하는게 보기 좋습니다.
절차
- 소스 클래스의 각 public 메서드에 대응하는 메서드들을 타깃 클래스에 생성한다. 이 메서드들은 단 순히 작업을 소스 클래스로 위임해야 한다.
- 소스 클래스의 메서드를 사용하는 코드를 모두 타깃 클래스의 위임 메서드를 사용하도록 바꾼다.
- 소스 클래스의 메서드와 필드를 모두 타깃 클래스로 옮긴다.
알고리즘 교체하기
- 리팩터링 전
def foundPerson(self, people):
for i in range(len(people)):
if people[i] == ‘Don’:
return ‘Don’
if people[i] == ‘John’:
return ‘John’
if people[i] == ‘Kent’:
return ‘Kent’
reurn ‘’
- 리팩터링 후
def foundPerson(self, people):
candidates = [‘Don’, ‘John’, ‘Kent’]
return people if people in candidates else ‘’
배경
어떤 목적을 달성하는 방법에는 여러가지가 있고, 그 중에서는 다른 것보다 더 쉬운 방법이 있기 마련입니다. 리팩터링을 하면 복잡한 대상을 단순한 단위로 나눌 수 있지만, 떄로는 알고리즘 전체를 걷어내고 훨씬 간단한 알고리즘으로 바꿔야 할 때가 있습니다.
이러한 작업을 진행하기 전에 메서드가 가능한 잘게 나눴는지 확인할 필요가 있습니다. 거대하고 복잡한 알고리즘을 교체하기란 상당히 어려우니 알고리즘을 간소화하는 작업부터 해야 교체가 쉬워집니다.
절차
- 교체할 코드를 함수 하나에 모은다 .
- 이 함수만을 이용하여 동작을 검증하는 테스트를 마련한다.
- 대채할 알고리즘을 준비한다.
- 정적 검사를 수행한다.
- 기존 알고리즘과 새 알고리즘의 결과를 비교하는 테스트를 수행하한다.
Leave a comment