개요 학부 시절 만들고자 하는 서비스는 많았는데 나의 서비스 개발 기술은 없는 수준이었다. 언어 기본기 공부, 프레임워크 공부를 최우선으로 생각해서, 솔직히 디자인패턴은 눈에 잘 안 들어왔다.
개발자로 2년을 근무하다 오랜만에 디자인패턴 글을 읽어보니 상당히 익숙해서 놀라웠다. 현업에서 자주 보았던 패턴이었으며, 나 역시도 무의식적으로 비슷하게 사용한 경우가 많았다.
방향성
프론트엔드 개발에서 자주 사용하는 JS + 취미에 있어 관심있는 메이플스토리의 조합으로 글을 새로 쓸 계획
외국 개발자의 저널을 https://dev.to/twinfred/design-patterns-in-javascript-1l2l 참고
내가 생각하는 디자인패턴이란 워낙에 추상적인 개념이다보니, 정답을 단정짓기는 그렇고 내 생각만 적어보자면, 디자인패턴의 주 목적은 결국 효율성을 최대화하기 위함이다. (의사소통, 개발 속도, 프로덕트 퀄리티, 유지보수 용이성)
ex) 변수 이름에도 camelCase, PascalCase, snake_case, pothole_case, kebab-case를 채택하면 여러 사람 사이에 직관적으로 표준이 생김
ex) 워크맨을 보다 알았는데 편의점에서 물건이 정면을 보도록, 가격표가 물건 왼쪽에 위치하도록 정렬하는 작업을 페이스업이라고 지칭한다.
“누구님 우유 코너 물건이 정면을 보도록, 가격표가 물건 왼쪽에 위치하도록 정렬해주세요” 이 타령을 하는 것보다 “누구님 우유 코너 페이스업 해주세요” 가 효율적임
이처럼 변수 표기법이나 편의점 물건 진열에도 사용하는데 몇 만줄이 되는 프로그램에서는 당연히 필요로 하는 것
Constructor(생성자), Builder(빌더) 패턴 JS에는 인스턴스를 쉽게 생성할 수 있는 도구(생성자함수, class)가 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Character { constructor (name, level, classes, guild ) { this .name = name; this .level = level; this .classes = classes; this .guild = guild; } } class Guild { constructor (name, master ) { this .name = name; this .master = master; } } const hero_1 = new Character ("히어로짱짱" , 10 , "hero" , null );const deven_1 = new Character ("데벤짱짱" , 10 , "demon avenger" , null );console .log (hero_1, deven_1);const iron = new Guild ("iron" , deven_1);console .log (iron);hero_1.guild = iron; console .log (hero_1);hero_1.guild = null ; console .log (hero_1);
Factory(팩토리) Pattern Constructor(생성자), Builder(빌더) 패턴에서 보듯이 JS는 객체를 생성하기 위해 생성자함수, class를 사용한다.
이런 객체 생성 도구들을 한 곳에 모아놓고 [도구1, 도구2, 도구3, 도구4, …] 인풋을 받아 한 도구만을 사용해 하나의 객체를 return하는 것이 팩토리 패턴이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 const mySkillSet = { level_5 : { frenzy : 30 , blood_fist : 20 , dimension_sword : 30 }, level_4 : { execution : 30 , shield_chasing : 29 , armor_break : 20 }, }; function skillFactory ({ skillSet } ) { this .getSkill = function ({ skill_level } ) { let skill; switch (skill_level) { case "level_5" : skill = new Skill5 ({ skillSet }); break ; case "level_4" : skill = new Skill4 ({ skillSet }); break ; } return skill; }; } const Skill5 = function ({ skillSet } ) { const { frenzy = 1 , blood_fist = 1 , dimension_sword = 1 } = skillSet.level_5 ; this .frenzy = frenzy; this .blood_fist = blood_fist; this .dimension_sword = dimension_sword; this .attack1 = () => { console .log ("frenzy" , "level" + frenzy); }; this .attack2 = () => { console .log ("blood_fist" , "level" + blood_fist); }; this .attack3 = () => { console .log ("dimension_sword" , "level" + dimension_sword); }; }; const Skill4 = function ({ skillSet } ) { const { execution = 1 , shield_chasing = 1 , armor_break = 1 , } = skillSet.level_4 ; this .execution = execution; this .shield_chasing = shield_chasing; this .armor_break = armor_break; this .attack1 = () => { console .log ("execution" , "level" + execution); }; this .attack2 = () => { console .log ("shield_chasing" , "level" + shield_chasing); }; this .attack3 = () => { console .log ("armor_break" , "level" + armor_break); }; }; const factory = new skillFactory ({ skillSet : mySkillSet });const skill_level_5 = factory.getSkill ({ skill_level : "level_5" });skill_level_5.attack1 (); skill_level_5.attack2 (); skill_level_5.attack3 (); const skill_level_4 = factory.getSkill ({ skill_level : "level_4" });skill_level_4.attack1 (); skill_level_4.attack2 (); skill_level_4.attack3 ();
Prototype(프로토타입) Pattern 객체마다 너무나도 동일한 특성을 띄고 있는 속성/메서드가 있다면, 그 속성/메서드는 굳이 각각의 객체마다 메모리를 잡고 있을 이유가 없다. JS에서는 생성자 함수에 .prototype을 사용해 미래에 생성될 객체들의 공통 속성/메서드를 메모리 하나에 정의할 수 있고 object에도 .__proto__ 속성을 사용해 공통으로 속성을 받아올 부모 object를 가리킬 수 있다. (참고로 JS에서 이렇게 object가 부모 참조를 시작하면 최종적으로 올라가는 곳은 Object 객체이다)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Character { constructor (name, level, classes, guild ) { this .name = name; this .level = level; this .classes = classes; this .guild = guild; } } const warrior = { classes_base : "warrior" , aura_weapon ( ) { console .log ("aura weapon" ); }, body_of_steel ( ) { console .log ("body of steel" ); }, }; const hero_1 = new Character ("히어로짱짱" , 10 , "hero" , null );const deven_1 = new Character ("데벤짱짱" , 10 , "demon avenger" , null );hero_1.__proto__ = warrior; console .log (hero_1.__proto__ === warrior);console .log (deven_1.__proto__ === warrior);warrior.name_kor = "전사" ; console .log (hero_1.classes_base );console .log (deven_1.classes_base );
Singleton(싱글톤) Pattern 실행 환경에서 어떤 생성자로부터 생성된 객체가 단 하나만 존재하는 패턴이다. 비유를 하자면 메이플 내 운영자 NPC는 업데이트 주기가 매우 길고, 사료를 주는 기간이 아니면 각각의 클릭 이벤트마다 서버와의 연동은 필요하지 않다. 운영자 인스턴스를 재클릭하면 기존에 존재하는 객체를 참조하여 반환한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const Master = (function ( ) { let instance; function createInstance ( ) { return { name : "운영자" , action1 : "이벤트 1에 대한 설명" , action1 : "이벤트 2에 대한 설명" , }; } function getInstance ( ) { if (!instance) { instance = createInstance (); } return instance; } return getInstance; })(); const instance1 = Master .getInstance ();const instance2 = Master .getInstance ();console .log (instance1 === instance2);