언제나 문제는 프로그램의 덩치가 커질 때 발생한다.
- 자바스크립트에서 객체지향을 하는 게 맞나요? | 요즘IT
var num = 1;
num = num + 1;
console.log(num);
GOTO
문을 통해 코드 흐름을 강제적으로 특정 코드 위치로 이동시키는 방식을 만들어 사용하게 된다.GOTO
를 통해서 코드 흐름을 강제적으로 바꾸는 방식이 가독성을 떨어뜨렸고 유지보수를 어렵게 만들었다. → 스파게티 코드가 되어버렸고 도저히 감당할 수 없다.프로시저
라는 개념이 발전한다.모두 인자를 받아 특정 작업을 수행하는 프로시저와 함수가 왜 다른 개념인지 이해하기 쉽지 않았다.
프로시저는 리턴 값이 없고, 함수는 리턴 값이 있다
라는 설명을 많이 봤지만 그게 충분한 설명이 되진 않았다.어떤 작업을 진행하는 순서
어떤 작업을 해야하는가
side effect 없이 온전히 입력값에 의해 출력값이 결정되며, 동일한 입력에 대해 동일한 출력을 내는 함수
function square(x) {
return x * x;
}
console.log(square(10)) // 출력: 100
square
함수는 출력값이 입력값만으로 결정되며, 그 값이 일정하다.
side effect가 있는 함수
var x = [1, 2, 3];
function sliceArr(n) {
if(x.includes(n)){
x = x.filter(v => v !==n);
return true;
}
return false;
}
console.log(sliceArr(2)); // true
console.log(x); // [1, 3]
sliceArr
함수는 입력값과 전역 변수 x
의 값에 영향을 받으면서, 실행 중에 직접적으로 x
의 값을 변경한다.절차적 프로그래밍은 프로그램의 흐름 제어와 재사용 측면에서 이점을 가져다주었다.
하지만 그럼에도 여전히 변수는 전역으로 관리되기 때문에 선언할 때마다 늘어나는 변수를 구분을 위해 prefix
를 붙여 사용하게 되었다.
prefix
만으로 변수를 구분해서 사용하는 것에 한계가 생기며, 파일 또는 모듈 단위로 prefix를 관리하는 namespace
라는 개념이 나타났다.namespace
를 사용하더라도 여전히 비슷한 형태의 데이터를 만들기 위해서는 개별적인 prefix가 필요하다는 한계가 있었다.user1_name = '고';
user1_age = 31;
user2_name = '지';
user2_age = 31;
구조체
를 만들어 관리하게 되었다.
구조체
는 관리하는 데이터가 비슷하다보니 자주 사용되는 함수들이 생겼고, 데이터와 함수를 함께 관리하려는 시도가 생겨났다. → 클래스
class User {
name ='이름'
age = 1
hello() {
console.log('안녕! ' + this.name)
}
}
오브젝트(object)
라고 부르게 되었다.객체지향 프로그래밍의 패러다임이 퍼져나가 널리 사용될수록 개발자들은 비슷한 문제를 겪게 되었다. 디자인 패턴은 이러한 일반적인 문제들에 대한 일반적인 해결책을 정리한 것이라고 할 수 있다.
이디엄
: 가장 기본적인 하위 설계 패턴
아키텍처 패턴
: 상위 설계 패턴, 가장 보편적
팩토리 메서드
문제 : 특정 객체에 대해서만 고려된 구조의 프로그램에 다른 유형의 객체가 추가되어야 하는 상황에서 기존 코드를 재사용하면서 프로그램을 확장할 방법이 필요해졌다.
생성자를 직접 사용하지 않고, 팩토리 메서드의 결과로 객체를 반환한다.
크리에이터
: 제품 인터페이스에 맞는 객체를 반환하는 팩토리 메서드구상 크리에이터
: concrete creator, 팩토리 메서드에 오버라이드해서 다른 유형의 객체를 만들 수 있도록 한다. 중요한 것은 제품 인터페이스의 공유다.제품 인터페이스
: 제품 객체가 가지는 공통 특성구상 제품
: 팩토리 메서드를 통해 만들어진 객체클라이언트 코드
: 팩토리 메서드를 사용하는 코드
class User { // 추상화되어 있는 인터페이스로 보자.
name = ''
age = 0
callName() {
console.log('이름 : ' + this.name + ', 나이 : ' + this.age)
}
}
function creator(name, age) {
let newUser = new User();
newUser.name = name;
newUser.age = age;
newUser.callName = function() {
console.log('안녕, 내 이름은 ' + this.name)
}
return newUser;
}
function concreteCreator(name, age) {
let newConcreteUser = creator(name, age);
newConcreteUser.callName = function() {
console.log('난 ' + this.name + '이고, ' + this.age + '살이야.')
}
return newConcreteUser;
}
let user1 = creator('고길동', 30);
let user2 = concreteCreator('둘리', 500);
user1.callName(); // 출력 : 안녕, 내 이름은 고길동
user2.callName(); // 출력 : 난 둘리이고, 500살이야.
user1
과 user2
는 모두 name
과 age
, callName
을 가지고 있지만 실제로 callName
은 다르게 동작한다.creator
와 concreteCreator
를 이용해서 같은 속성을 지녔지만 다르게 동작하는 객체를 만들 수 있게 됨으로써, 구상 크리에이터 외에는 별도 작업 없이 객체 유형의 추가를 통한 프로그램의 확장이 가능해졌다.추상 팩토리
서로 다른 인터페이스의 객체들이 변형에 따라 세트로 동작해야하는 경우에는 추상 팩토리 패턴을 적용할 수 있다.
class Cup {
design = ''
pack() {
console.log(this.design + '컵을 포장합니다.')
}
}
class Coaster {
design = ''
pack() {
console.log(this.design + '컵받침을 포장합니다.')
}
}
function createModernCup() {
let cup = new Cup();
cup.design = 'modern';
return cup;
}
function createClassicCup() {
let cup = new Cup();
cup.design = 'classic';
return cup;
}
function createModernCoaster() {
let coaster = new Coaster();
coaster.design = 'modern';
return coaster;
}
function createClassicCoaster() {
let coaster = new Coaster();
coaster.design = 'classic';
return coaster;
}
class AbstractFactory {
cup = {}
coaster = {}
packageProduct() {
this.cup.pack();
this.coaster.pack();
}
}
function packageModern() {
let fac = new AbstractFactory();
fac.cup = createModernCup()
fac.coaster = createModernCoaster();
return fac;
}
function packageClassic() {
let fac = new AbstractFactory();
fac.cup = createClassicCup()
fac.coaster = createClassicCoaster();
return fac;
}
let product;
let choosenDesign = window.prompt('디자인을 선택해서 입력해주세요. (modern | classic)');
if(choosenDesign == 'modern'){
product = packageModern();
}else if(choosenDesign == 'classic'){
product = packageClassic();
}else{
console.log('디자인을 선택해주세요');
}
if(product) product.packageProduct();