서비스 개발을 한다면 인터페이스 활용은 거의 필수이다. 인터페이스의 버전이 올라가거나, 인터페이스가 레거시에서 차세대로 눈에 띄게 바뀌는 경우 인터페이스의 규격도 바뀌기 마련이다.
개인 개발, 소규모 개발에 있어서는 클라이언트의 코드를 바꿔주면 되겠지만 큰 규모의 프로젝트라면 클라이언트 코드를 바꿔버리기에는 위험성이 있다. 또 신규 인터페이스가 문제가 생긴다면 언제든 롤백을 할 준비도 돼있어야 한다.
실무에서 경험을 바탕으로 생각해본다면 A인터페이스를 사용하기 위해 공통 유틸에 a_using이라는 메서드를 두었다. 만약 업그레이드 버전인 B인터페이스로 변경해야 하는 상황이 생겼다면 b_using메서드를 새로 만드는 것이 아니라 a_using메서드 안에 연동방식 환경변수(레거시/차세대)를 두고 B인터페이스를 필요에 따라 사용할 수 있도록 한다.
//이벤트 대상인지 판단하여 return return { event_target: false }; };
// (신) 인터페이스 const b_interface = function ({ id, classes, made }) { //id, 직업, 생성일을 input으로 const input = { id, classes, made };
//...... //어떤 계산이나 DB/네트워크 작업이 있다고 가정함 //......
//이벤트 대상인지 판단하여 return return { event_planned: false }; };
consta_using = ({ id, classes, made_date }) => { if (env.event_check_api === "A") returna_interface(); elseif (env.event_check_api === "B") { //input을 신규 인터페이스에서 요구하는 규격에 맞추었다. //이 과정이 Adapt라는 추상적인 표현으로 불림 const b_instance = b_interface({ id, classes, made: made_date });
//output 역시 신규 인터페이스에서 제공한 규격을 기존 규격으로 변경해주었다. //이 과정도 Adapt라는 추상적인 표현으로 불림 return { event_target: b_instance.event_planned }; } };
//a_using에는 Adapt 기능이 있으므로 //클라이언트 내 a_using을 호출하는 수십 군데 이상에서는 기존과 같이 사용하면 된다. const { event_target } = a_using({ id: "5324234124235324", classes: "hero", made_date: "20201214", });
console.log(event_target);
Composite(컴포지트) Pattern
객체가 여러 개 있다고 할 때, 각 객체 간 연관이 있을 수도 있고 없을 수도 있다. 연관이 있다면 그 사이에 어떻게 의미 부여를 할지 코드로써 나타낼 수 있다.
공장의 파이프라인과 같이 일의 순서가 중요한 관계라면 연결리스트(linked list)의 방향으로 설정해야 할 것이고 조직도와 같이 상하관계가 명백하면서 1대N으로 퍼지는 관계라면 트리로써 개발해야 할 것이다
컴포지트 패턴은 그 중 트리 자료구조를 활용하는 패턴이다. 아래 예시에서는 메이플 내에 지도(맵)을 활용하려고 한다.
맵 구성도 : ROOT => 각 차원 => 각 차원 내의 마을들 => 마을 안의 20~30개 정도 사냥터
시스템 내 1개 이상의 인터페이스를 수정/보완/결합하여 새로운 인터페이스를 만들고 공통으로 사용할 수 있는 제공하는 패턴이다.
어떤 수준의 인터페이스를 다뤄야 퍼사드라고 부를지는 프로젝트마다 다를 것이다. (소셜 로그인 API, 자체 API, 스크롤 옵저버, modal 창 이벤트, 버튼 클릭 이벤트까지 인터페이스는 다양하기 마련이다.)
프론트엔드 실무를 하며 가장 효율적이라고 느낀 퍼사드 패턴은 서버와의 네트워킹을 표준화한 것이었다.
유플러스 web 개발에서는 일반적인 FE프로젝트와 같이 axios모듈을 사용했으나, 대규모 프로젝트인만큼 클라이언트-서버 간 통신에서 엄연히 지켜야 할 규격이 존재했다. 특히 다른 시스템에서는 아예 사용하지 않는 유플러스만의 규칙도 존재했다. (자세한 것은 내부 정보라 말할 수 없음)
이것을 매번 숙지하여 header, parameter를 적절히 암호화하고, form에 딱 맞게 변환하는 작업들은 비생산적이다. 모두 같은 규칙의 작업이 이루어 진다면 공통으로 한번만 작업하는 것이 효율적이고, 후에 유지보수하기도 좋다.
아래 예시에서 메이플 이벤트 정보를 받아오는 서버를 표현했다. header에서 페이징 정보는 base64 encode 상태로 서버와 통신한다. 이것의 encode, decode가 퍼사드 내에서 이루어지도록 구현해보았다.
아래 예제에는 스킬들이 특정 쿨타임이 지나지 않으면 발동할 수 없도록 Proxy 함수를 구현했다. 예제에서 GUI나 사용자 입력은 구현이 번거로우므로 랜덤 스킬을 선택해 발동한다. Proxy 함수 내 캐시에는 최근 스킬 발동 시간을 저장한다. 스킬 호출 시간과 캐시에 저장된 시간의 차이가 200ms보다 클 때만 스킬을 사용한다. 200ms 쿨타임이 지나지 않으면 사용할 수 없음 문구를 출력한다.
// 쿨타임이 있는 스킬 사용하는 API functionSkillUsingAPI() { this.getValue = function (coin) { switch (coin) { case"frenzy": returnnewDate().getTime(); case"execution": returnnewDate().getTime(); case"shield_chasing": returnnewDate().getTime(); } }; }
// 스킬 사용 API를 우회하여 접근하는 함수 functionSkillUsingAPIProxy() { this.api = newSkillUsingAPI(); this.cache = {};
this.getValue = function (skill) { //쿨타임은 실제로 모두 다르겠으나, 테스트를 위해 편의상 200ms로 통일해둠 if (this.cache[skill] && newDate().getTime() - this.cache[skill] < 200) { console.log(`...... ${skill} 쿨타임이 지나지 않아 사용할 수 없습니다.`); } else { this.cache[skill] = this.api.getValue(skill); console.log( `++++++ ${skill} 사용완료, 최근 사용 시간 ${this.cache[skill]}` ); }