//클라이언트 let manager = aPerson.manager; class Person{ get manager(){return this._department.manager;} } class Department{ get manager(){return this._manager;} }
//클라이언트 let manager = aPerson.department.manager; class Person{ //게터 추가 후 클라이언트 코드 수정 후 위임(중개) 메서드 제거 get department(){return this._department;} } class Department{ get manager(){return this._manager;} }
class Person{ constructor(name){ this._name = name; } get name(){return this._name;} get department(){return this._department;} set department(arg){this._department = arg;} } class Department{ get chargeCode(){return this._chargeCode;} set chargeCode(arg){this._chargeCode=arg;} get manager(){return this._manager;} set manager(arg){this._manager=arg;} } //클라이언트 let manager = aPerson.department.manager;
class Person{ constructor(name){ this._name = name; } get name(){return this._name;} get department(){return this._department;} set department(arg){this._department = arg;} get manager(){return this.department.manager;} } class Department{ get chargeCode(){return this._chargeCode;} set chargeCode(arg){this._chargeCode=arg;} get manager(){return this._manager;} set manager(arg){this._manager=arg;} } //클라이언트 let manager = aPerson.manager;
class Person{ constructor(name){ this._name = name; } //접근자삭제 get name(){return this._name;} get manager(){return this.department.manager;} } class Department{ get chargeCode(){return this._chargeCode;} set chargeCode(arg){this._chargeCode=arg;} get manager(){return this._manager;} set manager(arg){this._manager=arg;} } //클라이언트 let manager = aPerson.manager;
//현재 제일을 못하는 클래스라 가정 class TrackingInformation{ get shippingCompany(){return this._shippingCompany;} set shippingCompany(arg){this._shippingCompany = arg;} get trackingNumber(){return this._trackingNumber;} set trackingNumber(arg){this._trackingNumber = arg;} get display(){ return `${this.shippingCompany} : ${this.trackingNumber}`; } } class Shipment{ get trackingInfo(){ return this._trackingInformation.display; } get trackingInformation() {return this._trackingInformation;} set trackingInformation(aTrackingInformation) { this._trackingInformation = aTrackingInformation; } } //클라이언트 aShipment.trackingInformation.shippingCompany = request.vender;
class Shipment{ get trackingInfo(){ return this._trackingInformation.display; } get shippingCompany(){return this._trackingInformation.shippingCompany;} set shippingCompany(arg){this._trackingInformation.shippingCompany = arg;} get trackingInformation() {return this._trackingInformation;} set trackingInformation(aTrackingInformation) { this._trackingInformation = aTrackingInformation; } } //클라이언트- 호출부분에서 TrackingInformation 제거 let aShipment = new Shipment(); aShipment.shippingCompany = request.vender;
class Shipment{ get trackingInfo(){ return this._trackingInformation.display; } get display(){ return `${this.shippingCompany} : ${this.trackingNumber}`; } //나머지도 옮긴 후 클래스 TrackingInformation 제거 get shippingCompany(){return this._shippingCompany;} set shippingCompany(arg){this._shippingCompany = arg;} get display(){ return `${this.shippingCompany} : ${this.trackingNumber}`; } get trackingInformation() {return this._trackingInformation;} set trackingInformation(aTrackingInformation) { this._trackingInformation = aTrackingInformation; } } let aShipment = new Shipment(); aShipment.shippingCompany = request.vender;
class Order{ constructor(data){ this._priority = data.priority; } //변수부터 캡슐화, 나중에필드이름이변경되도클라이언트코드는유지된다. get priority(){return this._priority;} set priority(arg){this._priority = arg;} }
//우선순위 속성을 표현하는 값 클래스 정의 class Priority{ constructor(value){ this._value = value; } //get value() 가 아닌 변환함수 사용 toString(){return this._value;} }
class Order{ constructor(data){ this._priority = data.priority; } //정의한 클래스 사용하도록 변경 get priority(){return this._priority.toString();} set priority(aString){this._priority = new Priority(aString);} }
//우선순위 자체가 아닌 우선순위를 표현하는 문자열 표현 class Order{ constructor(data){ this._priority = data.priority; } get priorityString(){return this._priority.toString();} set priority(aString){this._priority = new Priority(aString);} } class Priority{ constructor(value){ this._value = value; } toString(){return this._value;} } let highPriorityCount = orders.filter(o => "high" === o.priorityString || "rush" === o.priorityString) .length;
추가리팩터링
//Priority 클래스를 직접 사용하는 것이 좋을 것 같아 추가 개선 class Order{ constructor(data){ this._priority = data.priority; } get priority(){return this._priority;} get priorityString(){return this._priority.toString();} set priority(aString){this._priority = new Priority(aString);} } class Priority{ constructor(value){ if(value instanceof Priority) return value; if(Priority.legalValues().includes(value)) this._value = value; else throw new Error(`<${value}>는 요효하지 않은 우선순위입니다.`); } toString(){return this._value;} get _index(){return Priority.legalValues().findIndex(s=>s===this._value);} static legalValues(){return ['low','normal','high','rush'];} equals(other){return this._index === other._index;} higherThan(other){return this._index > other._index;} lowerThan(other){return this._index < other._index;} compareTo(other){return this._index - other._index;} } let highPriorityCount = orders.filter(o => o.priority.higherThan(new Priority("normal"))) .length;
class Person{ constructor(name){ this._name = name; this._courses = []; } get name(){return this._name;} get courses(){return this._courses;} set courses(arg){this.courses = arg;} } class Course { constructor(name, isAdvanced){ this._name = name; this._isAdvanced = isAdvanced; } get name(){return this._name;} get isAdvanced(){return this._isAdvanced;} } //클라이언트 Person이 제공하는 수업 컬렉션에서 수업 정보 얻음 let numAdvancedCourses; numAdvancedCourses = aPerson.courses .filter(c=>c.isAdvanced) .length;
//클라이언트 const basicCourseNames = readBasicCourseNames(filename); aPerson.courses = basicCourseNames.map(name => new Course(name, false)); //Person에서 게터로 수업컬렉션을 못가져오지만 //내가 수업 컬렉션을 세터로 설정하면, 수업 컬렉션을 수정해 캡슐화가 깨질 수 있다.
class Person{ constructor(name){ this._name = name; this._courses = []; } get name(){return this._name;} get courses(){return this._courses;} set courses(arg){this.courses = arg;} //제대로된 캡슐화를 위해 수업추가/제거 메서드 추가 addCourse(aCourse){ this._courses.push(aCourse); } removeCourse(aCourse, fnIfAbsent= () => {throw new RangeError();}){ const index = this._courses.indexOf(aCourse); if(index=== -1) fnIfAbsent(); else this._courses.splice(index,1); } }
//만들어둔 메서드를 사용해야해서, 이제 이방식으론 저장 못한다. // aPerson.courses = basicCourseNames.map(name => new Course(name, false)); for(const name of basicCourseNames){ aPerson.courses.addCourse(new Course(name,false)); }
//세터를 제거한다. 만약 제공해야한다면 복사본을 저장한다.권장하는것은세터제거 // set courses(aList){this.courses = _.cloneDeep(aList);}
//사본을 제공한다. get courses(){return _.cloneDeep(this._courses);}
class Person{ constructor(name){ this._name = name; this._courses = []; } get name(){return this._name;} get courses(){return _.cloneDeep(this._courses);} addCourse(aCourse){ this._courses.push(aCourse); } removeCourse(aCourse, fnIfAbsent= () => {throw new RangeError();}){ const index = this._courses.indexOf(aCourse); if(index=== -1) fnIfAbsent(); else this._courses.splice(index,1); } } class Course { constructor(name, isAdvanced){ this._name = name; this._isAdvanced = isAdvanced; } get name(){return this._name;} get isAdvanced(){return this._isAdvanced;} } let numAdvancedCourses; numAdvancedCourses = aPerson.courses .filter(c=>c.isAdvanced) .length; const basicCourseNames = readBasicCourseNames(filename); for(const name of basicCourseNames){ aPerson.courses.addCourse(new Course(name,false)); }
//이 상수는 레코드 구조로 사용되는 객체 const organization = {name:"애크미 구스베리", country:"GB"}; let result; result += `<h1>${organization.name}</h1>` //일기 organization.name = '새로운 이름' // 쓰기
//상수를 캡슐화하기, 이 게터는 임시 사용 function getRawDataOfOrganization() { return organization } let result; result += `<h1>${getRawDataOfOrganization().name}</h1>` //일기 getRawDataOfOrganization().name = '새로운 이름' // 쓰기
const organization = new Organization({ name: "애크미 구스베리", country: "GB" }); function getRawDataOfOrganization() {return organization._data;} function getOrganization(){return organization;} let result; result += `<h1>${getRawDataOfOrganization().name}</h1>` //일기 getRawDataOfOrganization().name = '새로운 이름' // 쓰기 //레코드를 캡슐화하는 목적은 변수를 통제하기 위함이다. //그러기 위해선 레코드를 클래스로 바꿔야한다. class Organization{ constructor(data){ this._data = data; } }
const organization = new Organization({ name: "애크미 구스베리", country: "GB" }); // function getRawDataOfOrganization() {return organization._data;} 정상동작하면 제거 function getOrganization(){return organization;} let result; result += `<h1>${getOrganization().name}</h1>` //일기 getOrganization().name = '새로운 이름' // 쓰기 //게터, 세터 생성 후 사용하도록 코드 수정 class Organization{ constructor(data){ this._data = data; } set name(aString){this._data.name = aString;} get name(){return this._data.name;} }
//현재 레코드를 그대로 받아 사용하고 있다. //레코드는 참조변수로 다루기에 캡슐화가 깨질 우려가 있다. 제거한다. //만일 _data를 그대로 쓸 것이라면 복제해서 써야한다. class Organization{ constructor(data){ this._name = data.name; this._country = data.country; } set name(aString){this._name = aString;} get name(){return this._name;} set _country(aString){this._country = aString;} get _country(){return this._country;} }
예시: 중첩된레코드캡슐화하기
JSON 같은여러겹으로중첩된레코드에대한캡슐화
//중첩구조가 심할수록 데이터 일고/쓰기가 힘들어진다. customerData[customerID].usages[year][month] = amount;//쓰기 function compareUsage(customerID, laterYear, month){//읽기 const later = customerData[customerID].usages[laterYear][month]; const earlier = customerData[customerID].usages[laterYear-1][month]; return {laterAmount: later, change:later-earlier}; }
getRawDataOfCustomers()[customerID].usages[year][month] = amount;//쓰기 function compareUsage(customerID, laterYear, month){//읽기 const later = getRawDataOfCustomers()[customerID].usages[laterYear][month]; const earlier = getRawDataOfCustomers()[customerID].usages[laterYear-1][month]; return {laterAmount: later, change:later-earlier}; } //변수 캡슐화부터 시작 function getRawDataOfCustomers(){return customerData;} function setRawDataOfCustomers(arg){customerData = arg;}
function getCustomerData(){return customerData;} function getRawDataOfCustomers(){return customerData._data;}//기존 호환 function setRawDataOfCustomers(arg){customerData = new CustomerData(arg);} //데이터 구조를 표햔하는 클래스 정의 후 이를 반환하는 함수 만듦 class CustomerData{ constructor(data){ this._data = data; } }
setUsage(customerID, year, month, amount);//쓰기 //데이터 구조 안으로 들어가는 세터 함수화 function setUsage(customerID, year, month, amount) { getRawDataOfCustomers()[customerID].usages[year][month] = amount; }
//클래스로 함수 옮기기 class CustomerData{ constructor(data){ this._data = data; } setUsage(customerID, year, month, amount) { getRawDataOfCustomers()[customerID].usages[year][month] = amount; } }
class CustomerData{ constructor(data){ this._data = data; } setUsage(customerID, year, month, amount) { getRawDataOfCustomers()[customerID].usages[year][month] = amount; } //객체의 모든 필드가 불변이 아니라면, 캡슐화가 꺠질 수 있다. //그렇다고 customerData 를 사용하는 모든 코드를 확인 했는지 알 수가 없다. //복사본을 리턴해서 처리한다. get rawData(){ return _.cloneDeep(this._data); } }
프록시구현으로객체수정시예외를던지는방법도있다.
function compareUsage(customerID, laterYear, month){//읽기 const later = getCustomerData().usage(customerID,laterYear,month); const earlier = getCustomerData().usage(customerID,laterYear-1,month); return {laterAmount: later, change:later-earlier}; }
class CustomerData{ constructor(data){ this._data = data; } setUsage(customerID, year, month, amount) { getRawDataOfCustomers()[customerID].usages[year][month] = amount; } get rawData(){ return _.cloneDeep(this._data); } //읽기 처리 usage(customerID, year, month){ return this._data[customerID].usages[year][month]; } }
//사용자가 데이터를 요청해 그 값을 수정하면 캡슐화가 깨질 수 있다. //복사본으로 처리하는 게 가장 간단한 방식 function compareUsage(customerID, laterYear, month){ const later = getCustomerData().rawData[customerID].usages[year][month]; const earlier = getCustomerData().rawData[customerID].usages[year][month]; return {laterAmount: later, change:later-earlier}; }
//client1.js 기본 요금 계산 코드 const aReading = acquireReading(); const baseCharge = baseRate(aReading.month, aReading.year) *aReading.quantity;
//client2.js 차 세금 일부 면제 코드 const aReading = acquireReading(); const base = (baseRate(aReading.month, aReading.year) *aReading.quantity); const taxableCharge = Math.max(0, base - taxThreshold(aReading.year));
/** client3.js * 중복코드가 한 곳에 몰려있으면 함수 추출로 제거하면 된다. * 현재 중복 코드가 전체에 흩어져 있다고 가정한다. */ const aReading = acquireReading(); const basicChargeAmount = calculateBaseCharge(aReading); //이미 함수로 만든 코드가 발견됐다. function calculateBaseCharge(aReading){ return baseRate(aReading.month, aReading.year) * aReading.quantity; }
//먼저 입력 객체를 그대로 복사해 반환하는 함수를 만든다. function enrichReading(original){ const result = _.cloneDeep(original); return result; }
function enrichReading(original){ const result = _.cloneDeep(original); //미가공 측정값에 기본 소비량을 부가 정보로 덧붙임 result.baseCharge = calculateBaseCharge(result ); return result; }
//client2.js 차 세금 일부 면제 코드 const rawReading = acquireReading(); const aReading = enrichReading(rawReading); const base = aReading.baseCharge; const taxableCharge = Math.max(0, base - taxThreshold(aReading.year));
function enrichReading(original){ const result = _.cloneDeep(original); result.baseCharge = calculateBaseCharge(result); //함수 이동 result.taxableCharge = Math.max(0, result.baseCharge - taxThreshold(result.year)); return result; }
//client2.js 차 세금 일부 면제 코드 const rawReading = acquireReading(); const aReading = enrichReading(rawReading); const taxableCharge = aReading.taxableCharge;