최근 웹 하나를 mvc구조로 만들게 되었다.
어떻게 구현하는게 좋을까 고민하다가
mvc 중 model을 제외한 view와 controller를
export function으로 구현하기로 하였는데
function 방식의 크나큰 단점을 직접 느끼고 배운 것이 많아 글을 작성해본다.
객체지향 프로그래밍은 왜 하는 것일까?
를 같이 고민해보면 좋을 것 같다. 참고
gameController.js
function onGameStart() {
...
}
function initEvent() {
document.querySelector('.button').
addEventListener('click', gameStart);
}
export default gameController = {
initEvent,
}
외부에서 필요한 함수들만 controller에 담아 export하는 방식이다.
어디서든 import하여 controller를 사용할 수 있다는 장점이 있지만
사실 class 방식으로도 한 파일에 객체를 생성하고 export를 하면 똑같이 구현할 수 있으므로 사실 큰 장점은 아니라고 생각한다.
가장 큰 문제점은 확장 가능성을 하나도 생각하지 않고 구현하게 되어버린다는 것에 있다... function으로도 확장성을 구현할 수 있을텐데 왜 그렇게 되는 것일까?
function으로도 class의 extends와 같은 기능을 분명 구현할 수 있다.
하지만 class처럼 확장이 편하지는 않다.
그래서 일단은 현재 편한 방식인 확장을 고려하지 않는 방식 으로 구현하게 되고 함수는 추상화 수준이 낮아진다.
결과적으로 refactor를 하게 되었을 때 코드의 중복이 너무나도 많이 존재하게 되는 것이다.
또한 function으로 확장을 한다고 해도, 상위 function 파일의 네이밍을 고민해야되며(네임만으로 상위 파일이라는 것이 인식이 되어야함) A.js 파일은 B.js 파일의 확장
이라는 의미가 class의 extends 처럼 명시되지 않고 import로 구현되기 때문에 다른 개발자가 보았을 때 가독성 측면에서도 좋지 않다.
이번에 refactor하면서 결국에는 function 일부를 class로 바꾸었다.
나는 거의 전부를 class로 바꿔도 된다는 생각이 들었지만
그렇게 되면 너무 많은 부분을 바꿔야해서 일부를 바꾸게 되었다.
GameController.js
export default class GameController extends Controller {
constructor() {
super();
this.initEvent();
}
initEvent() {
document.querySelector('.button').
addEventListener('click', this.onGameStart);
}
onGameStart = () => {
...
}
}
Controller라는 상위 클래스의 기본동작을 물려받아
GameController라는 새로운 클래스로 확장시키는 방식이다.
이번 경험 이후 나는 util(or 정말 확장 가능성이 하나도 없는경우) 정도는 function으로 구현하더라도 나머지 대부분은 class로 해결이 된다고 생각한다.
extends는 상속
보다는 확장
이다.
그래서 항상 구현할 때 하위 클래스가 상위 클래스에 속하는지 생각해보자
is
키워드를 기억하자
GameController is Controller
?
참이라면 GameController
는 Controller
로부터 확장이 가능하다.
그렇다면 class로 모든 문제가 해결될까?
만약 특정 하위 controller에만 존재하는 initGameState() 라는 메서드가 있다면 어떻게 해야할까?
상위 controller에 initGameState()를 넣기에는 하위 controller가 모두 해당 메서드를 가지게 되니 문제가 생긴다.
여기서 다중 상속이 불가능한 기존 js class 방식의 문제점이 드러난다.
특정 클래스만이 가지는 행동들을 구현하기 위해서 mixin이 등장했다.
만약 GameController만이 initGameState() 메서드를 가지고
다른 Controller(ex. UserController)들은 가지지 않는다면
다음과 같이 구현할 수 있다.
const initGameStateMixin = (superClass) => class extends superClass {
initGameState() {
...
}
};
export default class GameController extends initGameStateMixin(Controller) {
constructor() {
super();
this.initEvent();
}
initEvent() {
document.querySelector('.button').
addEventListener('click', this.onGameStart);
}
onGameStart = () => {
...
}
}
const gameController = new GameController();
gameController.initGameState(); // 가능함
(또는 object.assign()으로 구현할 수 있다. 참고)
class의 extends는 is
키워드가 중요했지만
mixin은 has
키워드가 중요하다.
해당 클래스가 다른 클래스와는 다르게 특정 메서드를 가진다면
mixin을 생각해보자.
GameController has initGameState()
?
또는 class는 객체들의 대분류 mixin은 동작에 기반한 소분류로
생각하면 좋을듯