클로저

클로저는 자바스크립트의 중요한 개념 중 하나로 함수와 그 함수가 선언된 렉시컬 환경(Lexical Environment)의 조합을 의미한다. 클로저는 함수가 선언될 때 그 함수의 스코프에 있는 변수들을 기억하고, 함수가 호출될 때에도 그 변수를 참조할 수 있게 한다.

 

렉시컬 환경(Lexical Environment)

자바스크립트의 실행 컨텍스트에서 변수와 함수 선언의 스코프를 관리하는 내부 구조를 의미한다. 렉시컬 환경은 코드가 작성된 위치에 따라 스코프를 결정하는데, 이는 코드가 실행될 때가 아니라 작성될 때의 구조에 따라 스코프가 결정된다는 점이 중요하다.

 

렉시컬 환경의 구성 요소

렉시컬 환경은 두 가지 구성 요소로 이루어져 있다.

 

1. 환경 레코드(Environment Record)

현재 스코프에서 정의된 모든 변수와 함수 선언을 저장한다.

 

2. 외부 렉시컬 환경 참조(Outer Lexical Environment Reference)

외부 스코프에 대한 참조를 가진다. 이는 중첩된 함수에 내부 함수가 외부 함수의 변수에 접근할 수 있게 한다.

 

렉시컬 환경의 동작

function outerFunction() {
  let outerVariable = "I am an outer variable";

  function innerFunction() {
    let innerVariable = "I am an inner variable";
    console.log(outerVariable); // "I am an outer variable"
    console.log(innerVariable); // "I am an inner variable"
  }

  return innerFunction;
}

const closureFunction = outerFunction();
closureFunction();

 

outerFunction 실행 시

환경 레코드 : { outerVariable: "I am an outer variable" }

외부 렉시컬 환경 참조 : 전역 환경

 

innerFunction 실행 시

환경 레코드 : { innerVariable : "I am an inner variable" }

외부 렉시컬 환경 참조 : outerFunction의 렉시컬 환경

 

렉시컬 환경의 예제

let globalVariable = "I am a global variable";

function exampleFunction() {
  let localVariable = "I am a local variable";

  function innerFunction() {
    console.log(globalVariable); // "I am a global variable"
    console.log(localVariable); // "I am a local variable"
  }

  innerFunction();
}

exampleFunction();

 

전역 렉시컬 환경

환경 레코드 : { globalVariable : "I am a global variable" }

외부 렉시컬 환경 참조 : 없음

 

exampleFunction 실행 시 

환경 레코드 : { localVariable : "I am a local variable" }

외부 렉시컬 환경 참조 : 전역 환경

 

innerFunction 실행 시 

환경 레코드 : 비어 있음 (현재 함수에서 변수를 선언하지 않음)

외부 렉시컬 환경 참조 : exampleFunction의 렉시컬 환경

 

렉시컬 환경은 자바스크립트의 변수와 함수 스코프를 관리하는 내부 구조로 코드가 작성된 위치에 따라 스코프가 결정된다. 렉시컬 환경은 환경 레코드와 외부 렉시컬 환경 참조로 구성되어 있으며 이를 통해 함수가 선언된 위치의 변수에 접근할 수 있다. 클로저는 이러한 렉시컬 환경을 활용하여 함수가 실행된 이후에도 외부 변수에 접근할 수 있게 하는 개념이다.

 

 

클로저의 동작 원리

클로저는 함수가 정의된 시점의 외부 스코프의 변수에 접근할 수 있는 기능을 제공하는 함수이다. 

즉, 클로저는 함수가 생성될 때 외부 환경의 변수를 캡처하여 함수가 실행될 때에도 그 변수에 접근할 수 있게 한다.

 

자바스크립트에서 함수는 선언될 때마다 자신이 선언된 환경을 기억한다. 이 환경은 함수가 생성된 시점의 스코프와 변수를 포함하며 클로저는 함수가 실행된 이후에도 해당 환경을 유지하므로, 함수가 반환된 이후에도 그 환경에 접근할 수 있다.

 

function outerFunction() {
  let outerVariable = "I am an outer variable";

  function innerFunction() {
    console.log(outerVariable); // "I am an outer variable"
  }

  return innerFunction;
}

const closureFunction = outerFunction();
closureFunction(); // "I am an outer variable"

 

innerFunction은 outerFunction 내부에서 선언되었으며 outerFunction의 스코프에 접근할 수 있다. outerFunction이 반환된 이후에도 innerFunction은 outerVariable에 접근할 수 있으며 이 경우 innerFunction은 클로저를 형성한다.

 

클로저의 주요 특징

함수가 생성될 때의 스코프를 기억

함수가 정의된 위치의 외부 변수를 기억하고, 함수가 호출될 때 해당 변수에 접근할 수 있다.

 

은닉화(Encapsulation)

클로저를 사용하여 함수 외부에서 접근할 수 없는 프라이빗 변수를 만들 수 있다.

 

고차 함수

클로저는 함수를 반환하거나 함수의 인수로 함수를 받을 수 있는 '고차 함수'와 함께 자주 사용된다.

 

프라이빗 변수(Private Variable)

변수를 캡슐화하여 외부에서 변수에 직접 접근할 수 없도록 만드는 패턴이다.

예제 1)

function createCounter() {
  let count = 0;

  return {
    increment: function() {
      count++;
      console.log(count);
    },
    decrement: function() {
      count--;
      console.log(count);
    },
    getCount: function() {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
console.log(counter.getCount()); // 1

 

count 변수는 createCounter 함수 내에 선언되어 있으며, createCounter 함수 외부에서도 직접 접근할 수 없으며, increment, decrement, getCount 메서드만이 count 변수에 접근할 수 있다.

 

예제 2)

function factory_game(title) {
  return
   {
   
    get_title : function () {
      return title;
    },
    
    set_title : function(_title) {
      title = _title 
    }
  }
}


factorty_game 함수는 내부에 get_title, set_title 두 개의 내부 함수를 가지고 있다.


외부함수의 인수 title은 함수 안에서 지역변수로 사용되며 첫 번째 속성인 get_title 메서드가 반환하는 title은 인수의 값이 된다.

 

set_title은 매개변수로 _title을 가지며 이 매개변수를 가지고 title = _title로 저장하여 외부함수의 인수가 title이 된다.

 

elder_scroll = factory_movie('The Elder Scrolls V: Skyrim');
fallout = factory_movie('Fallout: New Vegas');

console.log(elder_scroll.get_title());
// 'The Elder Scrolls V: Skyrim' 출력
console.log(fallout.get_title());
// 'Fallout: New Vegas' 출력

 

 

부분 적용 함수(Partial Application)

클로저를 사용하여 함수의 일부 인수를 고정한 새로운 함수를 생성할 수 있으며, 이는 함수의 재사용성을 높이고, 코드의 간결성을 유지하는 데 유용하다.

 

function multiply(a) {
  return function(b) {
    return a * b;
  };
}

const double = multiply(2);
console.log(double(5)); // 10

const triple = multiply(3);
console.log(triple(5)); // 15

 

주의사항

메모리 관리

클로저는 함수가 참조하는 변수들을 계속 유지하므로, 과도하게 사용하면 메모리 누수가 발생할 수 있다. 불필요한 클로저 사용을 피하고 필요하지 않은 참조는 명시적으로 제거하는 것이 좋다.

 

성능

클로저를 사용할 때는 성능에 주의가 필요하다. 특히 큰 데이터 구조를 포함하는 클로저는 메모리 사용량을 증가시킬 수 있다.

 

중첩 함수와 클로저

function makeCounter() {
  let count = 0;

  return function() {
    count++;
    return count;
  };
}

const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

 

위 렉시컬 환경은 다음과 같이 동작한다.

 

makeCounter 실행 시

환경 레코드 : { count : 0 }

외부 렉시컬 환경 참조 : 전역 환경

 

반환된 함수(클로저) 실행 시

환경 레코드 : 비어 있음

외부 렉시컬 환경 참조 : makeCounter의 렉시컬 환경 

 


 

클로저는 자바스크립트의 강력한 기능 중 하나로 함수가 선언된 렉시컬 환경을 기억하고, 함수가 호출될 때에도 그 환경에 접근할 수 있게 한다. 이를 통해 변수 은닉화, 부분 적용 함수, 콜백 함수 등 다양한 활용이 가능하다.

 

클로저의 주의사항을 유념하여 적절히 사용하면 코드의 재사용성과 유지보성을 높일 수 있다.

728x90
반응형

var

앞에서 정리한 내용을 바탕으로 생각해 보면 var 변수보다는 let, const 변수를 사용하는 것이 의도치 않은 문제가 발생할 경우를 줄일 수 있을 것으로 보인다.

 

그럼에도 var 변수는 왜 존재하고 사용되는지 정리한다.

 

역사적 이유

자바스크립트의 초기 버전에는 let, const 키워드가 없었고 var 만이 유일하게 변수를 선언하는 방법이였다.

 

이후에 let, const 가 도입되면서 변수 선언에 더 나은 방법이 제공되었지만 기존의 코드를 유지보수하거나 과거의 자바스크립트 버전과 호환성을 유지하기 위해 여전히 var가 사용되는 경우가 있다.

 

레거시 코드

많은 기존의 자바스크립트 코드베이스가 var를 사용하여 작성되었다. 이 코드를 유지보수하거나 확장할 때 기존의 스타일을 유지하기 위해 var를 계속 사용하기도 한다.

 

또한 오래된 자바스크립트 엔진이나 환경에서는 let, cosnt를 지원하지 않을 수 있기 때문에 그런 환경에서 코드를 실행하기 위해서는 var를 사용할 수밖에 없다.

 

호환성

모든 자바스크립트 환경에서 var는 지원되기 때문에 가장 광범위한 호환성을 보장할 수 있다. 예로 들어서 아주 오래된 브라우저나 자바스크립트 엔진에서도 var를 사용할 수 있다.

 

정리

기존의 코드베이스의 작업이나 아주 오래된 엔진 환경에서 실행을 하기 위함이라면 var를 사용하는 건 어쩔 수 없는 선택이지만 최신 자바스크립트 코드 작성 시에는 가능하면 var보다는 let이나 const를 사용하는 것이 안정성과 예측가능성을 높이기 때문에 권장된다.

728x90
반응형

'Program Language > JavaScript' 카테고리의 다른 글

JavaScript #9 데이터 타입  (9) 2024.07.22
JavaScript #8 클로저(Closure)  (0) 2024.07.22
JavaScript #6 호이스팅(Hoisting)  (0) 2024.07.21
JavaScript #5 유효 범위, 스코프(Scope)  (0) 2024.07.21
JavaScript #4 변수  (1) 2024.07.21

호이스팅

변수나 함수 선언들이 포함된 범위 상단으로 끌어올려지는 동작을 말한다.

이는 코드의 실제 실행 순서와는 다르게 해석되어 변수와 함수의 선언만 끌어올려지고 초기화는 끌어올려지지 않는다는 점에서 의미를 가진다.

 

변수 호이스팅

변수의 선언은 호이스팅 되지만, 변수 초기화는 호이스팅 되지 않는다. 이는 변수 선언이 코드의 최상단으로 이동된 것처럼 동작하지만 초기화는 원래 위치에 남아 있음을 의미한다.

 

실제 코드

console.log(a); // undefined
var a = 10;
console.log(a); // 10

 

자바스크립트 엔진에 의해 해석된 코드

var a;
console.log(a); // undefined
a = 10;
console.log(a); // 10

 

a 변수의 선언만 끌어올려지고 초기화는 원래 위치에 남아있게 된다.

 

let, const 키워드의 경우 선언된 변수는 호이스팅 되지만 var와 달리 초기화 전에 해당 변수에 접근하면 '일시적 사각지대(TDZ, Temporal Dead Zone)'으로 인해서 ReferenceError가 발생한다. 

 

함수 호이스팅

함수 선언은 변수 선언과 다르게, 함수의 정의 전체가 호이스팅 된다. 이는 함수가 코드에서 선언된 위치와 상관없이 호출될 수 있음을 의미한다.

 

작성된 함수 코드

greet(); // "Hello, World!"

function greet() {
  console.log("Hello, World!");
}

 

실행되는 엔진의 해석

function greet() {
  console.log("Hello, World!");
}

greet(); // "Hello, World!"

 

함수 표현식 호이스팅

함수 표현식은 변수 선언과 유사하게 동작하여 함수 선언과는 다르게 함수의 정의가 호이스팅 되지 않는다.

 

함수 표현식 코드

sayHello(); // TypeError: sayHello is not a function

var sayHello = function() {
  console.log("Hello!");
};

 

실행되는 엔진의 해석

var sayHello;

sayHello(); // TypeError: sayHello is not a function

sayHello = function() {
  console.log("Hello!");
};

 

sayHello 변수의 선언만 호이스팅 되어 상단으로 간다. 초기화는 원래 위치에 남아있어 sayHello가 함수로 초기화되기 전에 호출하기 때문에 TypeError가 발생한다.

 

호이스팅 동작 원리

자바스크립트의 실행 콘텍스트가 생성되는 과정에서 발생한다. 

1. 생성 단계 : 변수와 함수 선언이 메모리에 저장되고 변수는 undefined로 초기화된다.

2. 실행 단계 : 코드를 순차적으로 실행하면서 변수에 값이 할당되고, 함수호출이 이루어진다.

 

작성된 코드

console.log(a); // undefined
foo();          // "foo called"
console.log(bar); // undefined
// bar(); // TypeError: bar is not a function

var a = 10;

function foo() {
  console.log("foo called");
}

var bar = function() {
  console.log("bar called");
};

console.log(a);  // 10
foo();           // "foo called"
bar();           // "bar called"

 

실행되는 엔진의 해석

 

var a;
var bar;

function foo() {
  console.log("foo called");
}

console.log(a);  // undefined
foo();           // "foo called"
console.log(bar); // undefined
// bar(); // TypeError: bar is not a function

a = 10;
bar = function() {
  console.log("bar called");
};

console.log(a);  // 10
foo();           // "foo called"
bar();           // "bar called"

 

따라서 의도하지 않은 문제가 발생하는 것을 막기 위해서는 호이스팅이 동작하는 방식에 대한 이해가 반드시 필요하다. 

 

호이스팅으로 인한 문제를 예방하기 위해서 let, const 변수를 사용하는 것이 권장되는 방식이며 var 사용 시 코드의 순서에 유의할 필요가 있다.

728x90
반응형

'Program Language > JavaScript' 카테고리의 다른 글

JavaScript #8 클로저(Closure)  (0) 2024.07.22
JavaScript #7 var 변수에 대해서  (0) 2024.07.22
JavaScript #5 유효 범위, 스코프(Scope)  (0) 2024.07.21
JavaScript #4 변수  (1) 2024.07.21
JavaScript #3 연산자  (1) 2024.07.21

스코프는 변수나 함수가 유효한 범위를 정의한다.

함수 스코프

함수 내부에서 선언된 변수의 유효 범위를 의미한다. 

var 키워드로 선언된 변수는 함수 스코프를 가진다는 의미는 변수가 함수 내 어디서든 접근 가능하지만 함수 외부에서 접근할 수 없음을 의미한다.

 

function Func() {
  var funcScopeVar = "I am inside a function";
  console.log(funcScopeVar); // "I am inside a function"
}

Func();
// console.log(funcScopeVar); // ReferenceError: functionScopedVariable is not defined

 

Func 함수 내에서 선언된 var 변수 funcScopeVar는 함수 내부에서만 접근이 유효하고 함수 외부에서 접근할 때는 ReferenceError가 발생한다.

 

블록 스코프

블록 스코프는 중괄호 {}로 묶인 코드 블록 내부에서 선언된 변수의 유효 범위를 의미한다.

let과 const 키워드로 선언된 변수는 블록 스코프를 가진다. 이는 변수가 블록 내부에서만 접근 가능하며 블록 외부에서는 접근할 수 없음을 의미한다.

 

if (true) {
  let blockScopedVariable = "I am inside a block";
  const anotherBlockScopedVariable = "I am also inside a block";
  console.log(blockScopedVariable);   // "I am inside a block"
  console.log(anotherBlockScopedVariable); // "I am also inside a block"
}

// console.log(blockScopedVariable);   // ReferenceError: blockScopedVariable is not defined
// console.log(anotherBlockScopedVariable); // ReferenceError: anotherBlockScopedVariable is not defined

 

조건문의 중괄호 내에서 선언된 let과 const 변수들은 내부에서만 유효하며 블록 외부인 중괄호 바깥에서 접근 시 ReferenceError가 발생한다.

 

var의 경우 조건문의 중괄호 내에서 선언되어도 외부에서 접근이 유요하다.

if (true) {
  var variable = "I am a var variable";
}

console.log(variable); // "I am a var variable"

 

호이스팅

var, let, const로 선언된 변수는 모두 호이스팅 되지만 let, const로 선언된 변수는 TDZ로 인해 초기화 전에 접근이 불가능하게 된다.

 

 

전역 스코프

변수를 전역 스코프에서 선언할 때 각 키워드의 동작에는 차이가 있다. 

전역 스코프에서 변수의 선언은 코드 전체에서 접근 가능한 범위를 의미하며 변수 선언 방식에 따라 전역 객체에 미치는 영향이 다르게 된다.

 

var

전역 변수로 선언된 var 변수는 전역 객체의 속성이 되며 같은 이름의 변수를 여러 번 선언할 수 있다.

var globalVar = "I am a global var";
console.log(window.globalVar); // "I am a global var"

var globalVar = "I am a redefined global var";
console.log(window.globalVar); // "I am a redefined global var"

 

같은 이름의 변수를 여러 번 선언이 가능하다는 점은 코드가 복잡해질수록 중복된 이름을 사용하여 의도하지 않게 값을 변경하는 등의 문제를 발생시킬 수 있다.

 

또한 전역 객체의 속성이 되기 때문에 전역 네임스페이스 오염을 발생시켜 다른 스크립트나 라이브러리와 충돌할 위험이 있다.

 

var globalVar = "I am global";
console.log(window.globalVar); // "I am global"

 

let, const

let, const로 선언된 전역 변수는 전역 객체의 속성이 되지 않으며 같은 이름의 변수를 같은 스코프에서 두 번 선언할 수 없다.

 

여기서 같은 스코프란 전역 스코프로 다른 함수, 블록 스코프에서는 해당 이름을 사용할 수 있다. 이 경우 해당 스코프 내에서 선언된 변수로만 접근이 가능해지므로 전역에 존재하는 동일한 이름의 다른 변수는 접근할 수 없다.

 

let globalLet = "I am a global let";

function func() {
  let globalLet = "I am a local let";
  console.log(globalLet); // "I am a local let"
}

func();
console.log(globalLet); // "I am a global let"
728x90
반응형

'Program Language > JavaScript' 카테고리의 다른 글

JavaScript #7 var 변수에 대해서  (0) 2024.07.22
JavaScript #6 호이스팅(Hoisting)  (0) 2024.07.21
JavaScript #4 변수  (1) 2024.07.21
JavaScript #3 연산자  (1) 2024.07.21
JavaScript #2 표현식  (0) 2024.07.21

변수

자바스크립트에서 변수를 선언할 때 사용하는 키워드에는 var, let, const가 있다.

각 키워드는 변수의 스코프와 재할당 가능성, 호이스팅 방식에서 차이가 있다.

 

var

함수 스코프

var로 선언된 변수는 함수 스코프를 가지며 함수 내에서 선언된 변수는 함수 전체에서 접근할 수 있다.

 

호이스팅

var로 선언된 변수는 호이스팅 되며 선언이 코드의 최상단으로 끌어올려지고 초기화는 선언한 위치에서 이루어진다.

 

변수 재선언 가능

같은 스코프 내에서 여러 번 선언할 수 있다. 이때 이전 값은 덮어씌워지게 된다.

 

전역 객체에 속성으로 추가

전역 스코프에서 선언된 var변수는 전역 객체의 속성이 된다. (global 또는 window)

 

console.log(x); // undefined (호이스팅)
var x = 5;
console.log(x); // 5

if (true) {
  var y = 10;
}
console.log(y); // 10 (블록 스코프가 아님)

function foo() {
  var z = 20;
  console.log(z); // 20
}
foo();
// console.log(z); // ReferenceError: z is not defined (함수 스코프)

 

let 

블록스코프

let으로 선언된 변수는 블록 스코프를 가지고 블록 내에서만 접근할 수 있다.

 

호이스팅

let으로 선언된 변수는 호이스팅 되지만 선언하기 전에는 사용할 수 없으며 이를 '일시적 사각지대(TDZ, Temporal Dead Zone)'이라고 한다.

 

변수 재선언 불가

같은 스코프 내에서 두 번 선언할 수 없다.

// console.log(a); // ReferenceError: Cannot access 'a' before initialization (TDZ)
let a = 5;
console.log(a); // 5

if (true) {
  let b = 10;
  console.log(b); // 10
}
// console.log(b); // ReferenceError: b is not defined (블록 스코프)

let c = 15;
// let c = 20; // SyntaxError: Identifier 'c' has already been declared (재선언 불가)

 

const

블록스코프

const로 선언된 변수는 블록 스코프를 가진다.

 

호이스팅

const로 선언된 변수는 호이스팅 되지만, 선언하기 전에는 사용할 수 없다. TDZ에 영향을 받는다.

 

변수 재선언 불가

같은 스코프 내에서 두 번 선언할 수 없다.

 

상수

const로 선언된 변수는 초기화 후에 값을 변경할 수 없지만 객체나 배열의 속성은 변경할 수 있다.

 

// console.log(d); // ReferenceError: Cannot access 'd' before initialization (TDZ)
const d = 5;
console.log(d); // 5

if (true) {
  const e = 10;
  console.log(e); // 10
}
// console.log(e); // ReferenceError: e is not defined (블록 스코프)

const f = 15;
// const f = 20; // SyntaxError: Identifier 'f' has already been declared (재선언 불가)

// f = 25; // TypeError: Assignment to constant variable. (값 변경 불가)

const obj = { key: "value" };
obj.key = "new value"; // 객체의 속성은 변경 가능
console.log(obj.key); // "new value"

const arr = [1, 2, 3];
arr.push(4); // 배열의 요소는 추가 가능
console.log(arr); // [1, 2, 3, 4]
728x90
반응형

'Program Language > JavaScript' 카테고리의 다른 글

JavaScript #6 호이스팅(Hoisting)  (0) 2024.07.21
JavaScript #5 유효 범위, 스코프(Scope)  (0) 2024.07.21
JavaScript #3 연산자  (1) 2024.07.21
JavaScript #2 표현식  (0) 2024.07.21
JavaScript #1 개요  (0) 2024.07.21

연산자 기호는 공통적으로 사용되는 사칙연산을 적용하고 있다.

그 밖에 추가로 사용되는 연산자 중 차이점을 정리한다.

 

비교 연산자

동등 연산자와 일치 연산자

true == 1;
// true , 두 피연산자를 비교하기 전에 true를 1로 변환
true === 1;
// false , 두 피연산자의 타입부터 비교된다.

 

비슷해 보이는 두 연산자의 동일한 값 비교에서 명확한 차이가 난다.

 

 

'==' 동등 연산자

비교하는 두 값의 타입이 다르면 비교하기 전에 타입을 동일하게 변환 후 비교를 한다.

console.log(5 == '5');      // true, 숫자 5와 문자열 '5'가 타입 강제 변환 후 비교
console.log(null == undefined); // true, 둘 다 비어있는 값을 나타냄
console.log(0 == false);    // true, 숫자 0과 불리언 false가 타입 강제 변환 후 비교
console.log('' == false);   // true, 빈 문자열과 불리언 false가 타입 강제 변환 후 비교

 

'===' 일치 연산자

타입과 값이 모두 동일해야만 참을 반환한다.

console.log(5 === '5');     // false, 타입이 다르므로 일치하지 않음
console.log(null === undefined); // false, 타입이 다르므로 일치하지 않음
console.log(0 === false);   // false, 타입이 다르므로 일치하지 않음
console.log('' === false);  // false, 타입이 다르므로 일치하지 않음
console.log(5 === 5);       // true, 타입과 값이 모두 동일

 

타입 강제 변환의 수행 때문에 예기치 않은 결과를 발생시킬 수 있다. 따라서 자바스크립트에서는 일관성과 예측 가능한 동작을 위해 일반적으로 '==='를 사용하는 것이 권장된다.

 

<, > 등호 

숫자뿐만 아니라 문자열과 객체도 비교가 가능하다. 

console.log(5 < 10);   // true
console.log(5 > 10);   // false
console.log(10 > 10);  // false
console.log(10 < 10);  // false
console.log(10 <= 10); // true
console.log(10 >= 10); // true

 

 

문자열 비교는 유니코드 값을 기준으로 문자열을 비교한다.

console.log('apple' < 'banana');   // true
console.log('grape' > 'banana');   // true
console.log('apple' > 'Apple');    // true ('a'의 유니코드 값이 'A'보다 큼)
console.log('2' < '10');           // false (문자열 비교에서 '2'는 '10'보다 큼)

 

객체의 경우 직접 등호를 사용해서 비교할 수 없지만 비교 시 객체를 원시 값으로 변환하려고 시도한다. 이 경우 두 객체의 참조를 비교한다. 일반적으로 객체의 비교는 특정 속성을 비교하는 방법으로 사용한다.

// 객체 직접 비교시
let obj1 = { value: 10 };
let obj2 = { value: 20 };

console.log(obj1 < obj2); // false
console.log(obj1 > obj2); // false
console.log(obj1 == obj2); // false

// 객체의 속성 비교
let obj1 = { value: 10 };
let obj2 = { value: 20 };

console.log(obj1.value < obj2.value); // true

 

숫자와 문자열간의 비교도 가능하며 이 경우 자동으로 타입을 변환하여 비교한다.

console.log('5' < 10);  // true (문자열 '5'는 숫자 5로 변환됨)
console.log('5' > 10);  // false
console.log('5' > '10'); // true ('5'와 '10'은 문자열로 비교됨, '5'는 '10'보다 큼)

 

in 연산자

객체의 속성이나 배열의 인덱스가 존재하는지 확인하는 데 사용된다.

 

객체의 특정 속성이 존재하는지 확인할 때

let person = {
  name: "Alice",
  age: 30
};

console.log("name" in person); // true
console.log("age" in person);  // true
console.log("gender" in person); // false

 

배열의 특정 인덱스가 존재하는지 확인할 때

let numbers = [10, 20, 30];

console.log(0 in numbers); // true
console.log(1 in numbers); // true
console.log(3 in numbers); // false (인덱스 3은 존재하지 않음)

 

in 연산자는 주어진 속성 또는 인덱스가 객체의 자체 속성인지, 프로토타입 체인에 속한 속성인지 여부를 검사한다.

let person = {
  name: "Alice"
};

console.log("toString" in person); // true, toString은 Object.prototype의 속성

 

toString은 person 객체에 직접 정의된 속성이 아니지만 Object.protorype에 정의된 속성이기 때문에 in 연산자는 true를 반환한다.

 

객체의 자체 속성만을 검사하려면 Object.hasOwnProperty 메서드를 사용한다.

let person = {
  name: "Alice"
};

console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("toString")); // false

 

instanceof 연산자

특정 객체가 특정 생상저 함수 또는 클래스의 인스턴스인지 확인하는 데 사용된다. 이를 통해 객체의 프로토타입 체인을 검사하여 객체가 특정 생성자 함수의 프로토타입을 상속받았는지 여부를 확인할 수 있다.

object instanceof Constructor

 

여기서 object는 검사할 객체, Constructor는 생성자 함수이다.

 

원시 타입인 숫자, 문자열, 불리언 등에는 사용할 수 없다.

console.log(5 instanceof Number); // false
console.log("hello" instanceof String); // false

 

instanceof 연산자는 객체의 프로토타입 체인을 따라가면서 주어진 생성자 함수의 prototype 속성과 일치하는 프로토타입이 있는지 검사한다. 이 과정은 객체의 프로토타입 체인 끝에 도달할 때까지 계속된다.

 

function Foo() {}
function Bar() {}

let foo = new Foo();

console.log(foo instanceof Foo); // true
console.log(foo instanceof Bar); // false
console.log(foo instanceof Object); // true (Foo의 인스턴스는 Object의 인스턴스이기도 함)

 

따라서 객체의 프로토타입이 동적으로 변경된다면 instanceof의 결과도 달라질 수 있게 된다.

function Foo() {}
function Bar() {}

let obj = new Foo();
console.log(obj instanceof Foo); // true

Object.setPrototypeOf(obj, Bar.prototype);
console.log(obj instanceof Foo); // false
console.log(obj instanceof Bar); // true

 

typeof 연산자

typeof 연산자는 데이터 타입을 검사하는 데 사용되며 주로 원시 타입을 검사할 때 사용한다.

console.log(typeof 5);          // "number"
console.log(typeof "hello");    // "string"
console.log(typeof true);       // "boolean"
console.log(typeof {});         // "object"
console.log(typeof []);         // "object"
console.log(typeof function(){}); // "function"

 

논리 연산자

논리적인 조건을 평가하거나 결합하는 데 사용된다. 주로 조건문에서 사용되며 참 또는 거짓 값을 반환한다.

 

&& AND

두 피연산자가 모두 참일 때 참, 하나라도 거짓이면 거짓을 반환한다.

console.log(true && true);   // true
console.log(true && false);  // false
console.log(false && true);  // false
console.log(false && false); // false

let a = 5;
let b = 10;
console.log(a > 0 && b > 0); // true (a와 b가 모두 0보다 큼)

 

|| OR

두 피연산자 중 하나라도 참이면 참, 둘 다 거짓일 때만 거짓을 반환한다.

console.log(true || true);   // true
console.log(true || false);  // true
console.log(false || true);  // true
console.log(false || false); // false

let a = 5;
let b = -10;
console.log(a > 0 || b > 0); // true (a가 0보다 큼)

 

! NOT

피연산자의 부정을 반환한다. 참이면 거짓, 거짓이면 참을 반환한다.

console.log(!true);  // false
console.log(!false); // true

let a = 5;
console.log(!(a > 0)); // false (a > 0은 true이므로, !true는 false)

 

 

논리 연산자의 단축 평가 (Short-Circuit Evaluation)

논리 연산자는 단축 평가를 사용하여 불필요한 연산을 피한다. 이는 첫 번째 피연산자가 전체 표현식의 결과를 결정할 수 있는 경우 두 번째 피연산자를 평가하지 않는다는 것을 의미한다.

 

&&, 첫 번째 연산자가 거짓이면 두 번째 피연산자를 평가하지 않는다.

console.log(false && true);  // false (두 번째 피연산자를 평가하지 않음)
console.log(true && false);  // false
console.log(false && false); // false (두 번째 피연산자를 평가하지 않음)
console.log(true && true);   // true

let a = 5;
let b = 10;
console.log(a > 0 && b > 0 && b > a); // true (모든 조건이 참)

 

||, 첫 번째 피연산자가 참이면 두 번째 피연산자를 평가하지 않는다.

console.log(true || false);  // true (두 번째 피연산자를 평가하지 않음)
console.log(false || true);  // true
console.log(true || true);   // true (두 번째 피연산자를 평가하지 않음)
console.log(false || false); // false

let a = 5;
let b = -10;
console.log(a > 0 || b > 0 || b < a); // true (첫 번째 조건이 참이므로 나머지 조건을 평가하지 않음)

 

delete 연산자

객체의 속성을 삭제하는 데 사용된다. 주로 객체의 속성만을 삭제할 때 사용한다. 

 

delete object.property;
//또는
delete object['property'];

 

객체의 특정 속성을 삭제하고 삭제된 속성은 객체에서 완전히 제거되어 더 이상 해당 속성에 접근할 수 없다.

 

let person = {
  name: "Alice",
  age: 30
};

console.log(person.age); // 30

delete person.age; // age 속성 삭제

console.log(person.age); // undefined (삭제된 속성에 접근 시)
console.log(person); // { name: "Alice" }

 

배열의 요소 삭제

배열의 요소를 삭제할 수 있지만 배열의 길이는 변경되지 않고 해당 인덱스는 undefined로 남아 있게 되므로 완전히 제거하고 길이를 줄이기 위해서는 splice 메서드를 사용하는 것이 좋다.

 

let numbers = [1, 2, 3, 4];

delete numbers[2]; // 배열의 세 번째 요소 삭제

console.log(numbers); // [1, 2, undefined, 4]
console.log(numbers.length); // 4 (배열 길이는 그대로)

 

프로토타입 속성 삭제

객체의 프로토타입에 정의된 속성은 delete 연산자로 삭제할 수 없다.

 

function Person(name) {
  this.name = name;
}

Person.prototype.age = 30;

let alice = new Person("Alice");

console.log(alice.age); // 30 (프로토타입 속성)

delete alice.age; // 인스턴스의 age 속성 삭제 시도 (실제로 프로토타입의 속성은 삭제되지 않음)

console.log(alice.age); // 30 (프로토타입의 속성은 여전히 존재)

 

변수 삭제

전역 변수와 함수의 경우 delete 연산자를 사용해도 삭제되지 않는다. 지역 변수는 delete로 삭제할 수 없다.

 

 

728x90
반응형

'Program Language > JavaScript' 카테고리의 다른 글

JavaScript #6 호이스팅(Hoisting)  (0) 2024.07.21
JavaScript #5 유효 범위, 스코프(Scope)  (0) 2024.07.21
JavaScript #4 변수  (1) 2024.07.21
JavaScript #2 표현식  (0) 2024.07.21
JavaScript #1 개요  (0) 2024.07.21

표현식은 코드에서 값으로 평가될 수 있는 코드의 조각이라고 말할 수 있다.

표현식의 결과는 값이 될 수 있으며, 이를 통해 값을 할당하거나 다른 표현식의 일부로 사용할 수 있다.

 

표현식을 파악하면 자바스크립트의 문법이 어떤 코드의 조각들로 작성되어서 변수의 값이 할당되고 조건문 및 반복문 등이 구성되고 함수의 인수로 전달되는지 알 수 있다.

 

표현식 종류

리터럴 표현식

고정된 값을 나타낸다.

가장 간단한 형태로 다른 표현식을 포함하지 않는 독립적인 표현식이다.

1; // 숫자 리터럴
"hello" // 문자열 리터럴
true; //boolean 리터럴
[1,2,3]; // 배열 리터럴
{key:"value"}; // 객체 리터럴

 

식별자 표현식

변수나 상수의 이름을 나타낸다.

let x = 10;
x; // 식별자 표현식 값 : 10

 

 

연산자 표현식

연산자를 사용하여 값을 생성한다.

5 + 5;
x * 2;
y > 5;

 

함수 호출 표현식

함수를 호출하여 값을 생성한다.

function add(a, b){
	return a + b;
}
add(3, 4); // 함수 호출 표현식, 값 : 7

 

객체 프로퍼티 접근 표현식

프로퍼티에 접근하는 표현식이다.

let person = { name : "Bak", age: 25};
person.name; // 객체 프로퍼티 접근 표현식, 값 : "Bak"

 

산술 표현식

let a = 5;
let b = 10;
let sum = a + b; // 값 15

수로 변환 불가능한 피연산자인 경우 NaN(Not-a-Number) 값으로 변환되며 피연산자 중 하나라도 NaN일 경우 연산 결과는 NaN이 된다.

 

비교 표현식

let isEqual = (a === b);

 

논리 표현식

let isAdult = (age >= 25 && age < 100);

 

삼항 표현식

let access = (age >= 18) ? "Adult" : "None";

 

표현식 사용

변수 할당

let result = 5 + 10; // 연산자 표현식을 사용하여 result에 값 할당

 

조건문

let age = 25;
if (age >= 25) { // 비교 연산자 표현식을 사용한 조건문
	console.log("Bakcdoing's Blog");
}

 

함수 인수

fuction greet(name){
	console.log("Hello, " + name);
}
greet("Bak"); // 문자열 리터럴을 함수 인수로 사용
728x90
반응형

'Program Language > JavaScript' 카테고리의 다른 글

JavaScript #6 호이스팅(Hoisting)  (0) 2024.07.21
JavaScript #5 유효 범위, 스코프(Scope)  (0) 2024.07.21
JavaScript #4 변수  (1) 2024.07.21
JavaScript #3 연산자  (1) 2024.07.21
JavaScript #1 개요  (0) 2024.07.21

JavaScript

자바스크립트는 현재까지도 웹 개발에서는 빼놓을 수 없는 필수적인 기술이다. 더 나아가 이제는 웹뿐만 아니라 서버 개발에서도 사용할 정도로 높은 유연성과 확장성을 가진 언어로 평가된다.

 

탄생

1995년, 웹은 주로 정적인 페이지로 구성되어 있었고 이러한 페이지는 사용자가 상호작용할 수 있는 기능이 매우 제한적이었으며 또한 모든 상호작용은 서버와의 통신을 통해 이루어졌기 때문에 느리고 비효율적인 방식이었다.

 

당시 웹 브라우저 시장을 주도했던 미국의 소프트웨어 회사인 넷스케이프(Netscape Communications Corporation)는 이러한 문제점을 해결하고 사용자와 웹 페이지의 상호작용을 개선하기 위해서 클라이언트 측에서 실행될 수 있는 스크립팅 언어가 필요하다고 판단하였다. 

넷스케이프

 

넷스케이프의 CTO였던 마크 앤드리슨(Marc Andreessen)은 프로그래밍 언어 설계와 컴파일러 개발에 경험이 있던 브렌던 아이크(Brendan Eich)를 고용했으며 그의 풍부한 경험을 바탕으로 아주 짧은 기간인 10일 만에 넷스케이프의 요구를 충족하는 스크립팅 언어를 개발했고 그 초기 버전은 '모카(Mocha)'라는 이름이었다.

 

좌) 마크 앤드리슨 우) 브렌던 아이크

 

모카는 이후 개발과 사업적인 과정을 거치면서 '라이브스크립트(LiveScript)'로 바뀌었고 최종적으로는 자바스크립트(JavaScript)로 알려지게 되었다.

 

당시 넷스케이프와 경쟁 관계였던 마이크로소프트(Microsoft)는 자바스크립트와의 경쟁을 위해 자사의 브라우저인 인터넷 익스플로러 3.0에 자바스크립트의 변형인 JScript를 도입하면서 두 웹 브라우저 간의 경쟁이 치열해졌다. 그 밖에도 여러 브라우저들이 있었으며 하나의 브라우저에 맞춰서 작업을 하면 다른 브라우저에서는 제대로 동작하지 않는 경우가 빈번하게 발생하였고 이는 웹 개발자들에게 큰 불편을 초래하여 웹의 발전을 저해하는 요인이 되었다.

 

자바스크립트 표준화

이러한 호환성 문제를 해결하기 위해서 자바스크립트를 표준화하려는 노력이 시작되었다.

1996년 넷스케이프는 유럽 컴퓨터 제조업체 협회(ECMA)에 자바스크립트의 표준화를 제안하였고 ECMA는 자바스크립트를 표준화하기 위한 기술 위원회를 구성하였다. 

 

그리고 1997년 자바스크립트의 표준 사양인 ECMA-262를 발표했다. ECMAAScript로 알려진 이 표준은 자바스크립트와 JScript 간의 차이를 최소화하고, 두 브라우저 간의 호환성을 개선하는데 목적이 있었다. 이후에도 표준화 작업은 지속적으로 이루어져 자바스크립트의 공식 표준으로 웹 개발의 핵심을 이루었으며 최신버전인 2015년 ES6까지 이어지고 있다.

 

발전

자바스크립트는 동적 언어의 패러다임을 가지고 있으며 발전 과정에서도 그 틀은 바뀌지 않았다. 웹 페이지는 발전할 수 록 더 복잡해져 갔고 여기서 발생되는 가장 큰 불편은 데이터를 갱신할 때마다 서버로부터 새로 데이터를 가져오면서 발생하는 새로고침으로 웹 페이지의 데이터가 많을수록 점점 더 느려져갔다.

 

1999년 마이크로소프는 인터넷 익스플로러 5.0에서 XMLHttpRequest 객체를 도입하기 시작했다. 이 객체는 자바스크립트를 사용하여 서버와 비동기적으로 통신할 수 있게 해 주었다.

 

이 객체의 도입으로 사용자의 요청마다 전체 페이지를 새로 고침 해야 하는 페이지 리로드 문제, 새로 고침으로 인해 서버 응답 시간과 페이지 렌더링으로 인해 시간이 필요한 느린 반응 속도 문제 그리고 매번 전체 페이지를 새로 고침 하면서 서버에 불필요한 요청을 증가시켜 서버 부하를 높이던 문제를 해결할 수 있게 되었다.

 

또한 이제 동적으로 콘텐츠를 업데이트하는 것이 가능해졌기 때문에 XMLHttpRequest의 등장 이후로 사용자의 경험은 크게 향상되었다. 초기에는 이 기능이 주로 마이크로소프트의 ActiveX 기술을 통해 제공되었지만, 이후 다른 브라우저에서도 이 기능을 채택하게 되었다.

 

더 나아가서 비동기적 웹 기술의 발전은 2005년 제시 제임스 가렛(Jesse James Garrett)에 의해서 AJAX(Asynchronous JavaScript and XML), 자바스크립트를 사용하여 서버와 비동기적으로 통신하는 기술에 대한 개념을 정립하였다.

제시 제임스 가렛

 

이후로 더 상호적이고 반응성이 뛰어난 웹 애플리케이션들이 만들어지게 되었고 현재까지도 이 기술은 일반적으로 사용되는 개념이 되었다.

 

용도와 중요성

자바스크립트는 기본적으로 웹 페이지에 동적인 기능을 추가하는 역할을 한다. 따라서 웹 개발에서는 필수적으로 요구된 되는 기술로 반드시 학습하는 것이 좋다.

 

현재는 웹뿐만 아니라 기술이 확장되어서 Node.js와 같은 런타임 환경을 통해서 서버 측 개발에서도 널리 사용되고 있다. 이를 통해 백엔드 로직을 작성하고 데이터베이스와 상호작용하는 것도 가능하다.

 

또한 React Native와 같은 프레임워크를 통해 네이티브 모바일 애플리케이션을 개발하거나 Phaser와 같은 라이브러리로 2D 나 3D 게임까지도 만들 수 있다.

 

특징

자바스크립트의 가장 큰 언어는 동적언어라는 점이다.

 

동적언어란, 런타임 시에 다양한 동작을 수행할 수 있는 프로그래밍 언어를 말한다. 이러한 자바스크립트의 특징은 사용자와 실시간으로 다양한 상호작용을 주고받을 필요가 있는 웹 페이지에 적합하다.

 

동적 언어의 특징

동적 타입 바인딩

변수의 타입이 런타임에 결정된다. 즉, 변수를 선언할 때 특정 타입을 지정할 필요가 없으며, 실행 중에 할당된 값에 따라 타입이 변경될 수 있다.

 

만약 C, C++, Java 같은 정적 언어인 경우

int num = 1;
string name = "bak";

컴파일 타임에 타입을 확인하기 때문에 타입으로 인한 런타임 오류를 방지할 수 있으며 명시적으로 지정했기 때문에 코드의 가독성이 좋지만 그만큼 매번 타입을 지정해야 하는 번거로움이 있다는 특징이 있다.

 

반대로 동적언어인 JavaScript, Ruby, Python의 경우

var num = 1;
num = true;
num = "Hello";

런타임에서 타입이 결정될 수 있다. 따라서 매번 타입을 변경할 필요가 없기 때문에 var 변수를 사용해 모든 변수 선언이 가능하다. 이미 선언된 변수도 이후에 다른 타입을 값을 저장하는 것 또한 문제가 발생하지 않는다. 

 

동적언어와 비교했을 때 상대적으로 타입과 관련한 코드와 규칙이 적기 때문에 코드가 짧고 배우기가 쉽지만 실행 중 타입에러가 발생할 수 있는 문제도 존재한다.

추가적으로 C++의 auto 타입이 var와 비슷하기 때문에 동적인 특징이 있어 보이지만 auto 타입은 컴파일 단계에서 타입이 추론되기 때문에 런타임에서는 이미 타입이 확정된 상태이다.

 

인터프리터

자바스크립트는 기본적으로 인터프리터로 방식으로 실행된다. 현대에 들어서는 JIT(Just In Time) 컴파일을 활용하여 성능을 향상시기키도 했다. 

 

인터프리터 방식이란 코드를 한 줄씩 읽고 실행하는 방식으로 개발 중 빠른 피드백을 제공하고 동적 특성을 잘 처리할 수 있지만 성능 면에서 제한이 있었다. 이러한 문제점을 JIT 컴파일을 통해서 보완하여 사용 중이다.

728x90
반응형

'Program Language > JavaScript' 카테고리의 다른 글

JavaScript #6 호이스팅(Hoisting)  (0) 2024.07.21
JavaScript #5 유효 범위, 스코프(Scope)  (0) 2024.07.21
JavaScript #4 변수  (1) 2024.07.21
JavaScript #3 연산자  (1) 2024.07.21
JavaScript #2 표현식  (0) 2024.07.21

extern

extern 키워드를 사용하면 플랫폼 간 상호 운용성, 성능 최적화, 코드의 안전성과 에러 처리를 모색할 수 있다.

 

플랫폼 간 상호 운용성

C# 언어를 사용하는 프로젝트에서 이외의 언어로 작성된 라이브러리 함수를 호출하는 경우는 아주 흔하게 발생하는 상황이다. 예를 들어서 C#에서 Windows API를 호출하려면 DllImport 속성을 사용해서 C# 코드에서 네이티브 코드 함수를 선언해야 한다. 이를 통해 C# 프로그램에서 운영 체제 수준의 다양한 기능을 직접적으로 사용할 수 있다.

 

Windows API 중 몇 가지 간단한 함수를 사용해 본다.

namespace Test
{
    using System;
    using System.Text;
    using System.Runtime.InteropServices;

    internal class Program
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct SYSTEMTIME
        {
            public ushort Year;
            public ushort Month;
            public ushort DayOfWeek;
            public ushort Day;
            public ushort Hour;
            public ushort Minute;
            public ushort Second;
            public ushort Milliseconds;
        }

        [DllImport("Kernel32.dll")]
        public static extern bool SetConsoleTitle(string title);
        

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern uint GetConsoleTitle(StringBuilder buffer, uint size);

        [DllImport("Kernel32.dll")]
        public static extern void GetSystemTime(out SYSTEMTIME st);

        static void Main(string[] args)
        {
            SetConsoleTitle("Bak's Console");
            Console.WriteLine("Hello, World!");

            StringBuilder buffer = new StringBuilder(256); // Define buffer size according to your need
            GetConsoleTitle(buffer, (uint)buffer.Capacity);
            Console.WriteLine("Console Title: " + buffer.ToString());

            SYSTEMTIME st;
            GetSystemTime(out st);
            Console.WriteLine("Current System Time: {0}-{1}-{2} {3}:{4}:{5}.{6}",
                st.Year, st.Month, st.Day, st.Hour, st.Minute, st.Second, st.Milliseconds);

            Console.ReadLine();
        }
    }
}

 

실행 결과

C# - extern kernel32.dll

 

외부 함수 SetConsoleTitle을 사용해서 실행되는 콘솔의 타이틀을 변경하고 GetConsoleTitle을 사용해서 변경한 콘솔의 타이틀을 가지고 와서 콘솔에 찍어본다. 그리고 GetSystemTime을 사용해서 현재 실행 중인 장치의 시간을 가지고 온다.

 

코드를 보면 알 수 있듯이 필요한 기능을 사용하기 위해서는 어트리뷰트를 선언할 때 추가로 필요한 값들이나 사용하고자 하는 함수의 반환값, 필요한 파라미터 등에 대한 정보들이 필요하다.

 

이러한 정보들은 마이크로소프트 공식 문서나 또는 이를 주제로 하는 커뮤니티에서 확인할 수 있고 이외 라이브러리들도 제공하는 곳에서 API 문서를 확인할 수 있다.

 

성능 최적화

C# 코드 내에서 수학 계산이나 이미지 처리와 같은 고성능의 연산을 요구하는 네이티브 라이브러리 함수를 호출하는 방법이 있다. 이러한 방식은 매니지드 코드(Managed Code)에 비해 실행 속도가 빠른 네이티브 코드의 이점을 활용할 수 있게 해 준다.

 

네이티브 코드를 사용하는 것이 언제나 성능적으로 이점이 있다고 할 수는 없으므로 해당 목적을 위해서 외부 라이브러리를 사용한다고 했을 때에는 .Net 환경에서 이미 최적화된 상태인 C# 라이브러리의 성능과 비교해서 네이티브 코드를 사용했을 때에 성능적인 이점이 있을 때 사용하는 것이 좋다.

 

 

728x90
반응형

new

new 키워드의 사용을 최소화하여 객체 생성의 통제를 개선하고 코드의 유연성과 재사용성을 높여 의존성 관리를 용이하게 할 수 있다. 이를 통해서 메모리 사용을 최적화하고 객체의 생명주기를 효과적으로 관리할 수 있다.

 

디자인 패턴(Design Pattern)

주로 사용되는 방식들은 패턴으로 정형화된 구조를 만들 수 있다. 

자주 그리고 반복적으로 사용되는 코드의 디자인을 정형화된 패턴으로 만들 수 있는데 이를 디자인 패턴이라고 한다.

 

디자인 패턴은 다양한 종류들이 존재하는데 이번에는 그중 객체의 생성과 할당과 관련된 패턴들 중 대표적인 몇 가지만 살펴본다.

 

싱글턴 패턴(Singleton Pattern)

싱글턴 패턴은 클래스의 인스턴스가 하나만 존재하도록 보장하는 패턴이다.

이 패턴은 객체를 전역적으로 접근할 수 있게 하여 리소스에 대한 중복 접근을 방지하고 메모리 사용을 효율적으로 관리할 수 있도록 한다. 시스템 전체에 걸쳐 일관된 상태를 유지할 수 있으며 객체의 생성과 생명주기를 통제할 수 있다.

 

public class Singleton
{
    private static Singleton instance;
    private Singleton() {}
    
    public static Singleton GetInstance()
    {
        if (instance == null)
        {
            instance = new Singleton();
        }
        return instance;
    }
}

 

생성자를 private으로 선언하여 클래스 외부에서는 새 객체를 생성할 수 없도록 하며 GetInstance()를 통해서 유일하게 존재하는 클래스의 인스턴스에 접근할 수 있도록 한다.

 

팩토리 메서드 패턴(Factory Method Pattern)

객체 생성을 처리하는 인터페이스를 제공하면서 서브 클래스가 생성할 객체의 클래스를 지정할 수 있게 한다.

이 패턴을 통해서 객체 생성을 추상화할 수 있으며 시스템의 확장성과 유연성을 향상할 수 있다.

 

팩토리 패턴의 방법은 다음과 같다.

 

1. 생성할 객체들이 공통적으로 구현할 인터페이스를 정의한다. 

2. 인터페이스를 구현하는 클래스들을 생성한다. 각 클래스에서는 인터페이스의 메서드를 구체적으로 구현한다.

3. 객체 생성을 담당할 메서드를 포함하는 인터페이스를 정의한다. 이 메서드는 인터페이스 타입의 객체를 반환한다.

4. 생성자 인터페이스를 구현하는 클래스를 생성하고 팩토리 메서드를 오버라이드하여 인스턴스를 생성하고 반환한다.

5. 외부에서는 팩토리 메서드를 통해 객체를 생성하여 의존성 없이 동작할 수 있게 된다.

 

예시로 여러 종류의 몬스터를 만드는 팩토리 패턴을 활용하여 작성해 본다.

 

// 몬스터 인터페이스 정의
public interface IMonster
{
    void Attack();
}

// 구체적인 몬스터 클래스
public class Zombie : IMonster
{
    public void Attack()
    {
        Console.WriteLine("Zombie attacks!");
    }
}

public class Vampire : IMonster
{
    public void Attack()
    {
        Console.WriteLine("Vampire attacks!");
    }
}

// 몬스터 생성을 위한 팩토리 클래스
public abstract class MonsterFactory
{
    public abstract IMonster CreateMonster();
}

public class ZombieFactory : MonsterFactory
{
    public override IMonster CreateMonster()
    {
        return new Zombie();
    }
}

public class VampireFactory : MonsterFactory
{
    public override IMonster CreateMonster()
    {
        return new Vampire();
    }
}

class Program
{
    static void Main(string[] args)
    {
        MonsterFactory factory = new ZombieFactory();
        IMonster monster = factory.CreateMonster();
        monster.Attack();

        factory = new VampireFactory();
        monster = factory.CreateMonster();
        monster.Attack();
    }
}

 

 

특히 다양한 객체가 동일한 인터페이스를 공유하거나 시스템 구성 요소 간의 결합도를 낮추기 원할 때 유용하며 객체의 생성과 클래스의 구현을 분리함으로써 모듈화 된 코드 구조를 가능하게 한다.

 

빌더 패턴 (Builder Pattern)

빌더 패턴은 복잡한 객체의 생성과정을 단계별로 나누어 구축하는 디자인 패턴으로 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 다양한 표현 결과를 얻을 수 있도록 한다. 주로 복잡한 객체를 조립해야 할 때 사용되며 각 부분의 조립 순서를 외부에서 지정할 수 있다.

 

// 캐릭터의 인터페이스 정의
public interface ICharacter
{
    void EquipWeapon(string weapon);
    void EquipArmor(string armor);
    void AddAbility(string ability);
}

// 캐릭터 빌더 인터페이스
public interface ICharacterBuilder
{
    ICharacterBuilder BuildWeapon(string weapon);
    ICharacterBuilder BuildArmor(string armor);
    ICharacterBuilder BuildAbility(string ability);
    Character Build();
}

// 구체적인 캐릭터 빌더
public class HeroBuilder : ICharacterBuilder
{
    private Character _character = new Character();

    public ICharacterBuilder BuildWeapon(string weapon)
    {
        _character.EquipWeapon(weapon);
        return this;
    }

    public ICharacterBuilder BuildArmor(string armor)
    {
        _character.EquipArmor(armor);
        return this;
    }

    public ICharacterBuilder BuildAbility(string ability)
    {
        _character.AddAbility(ability);
        return this;
    }

    public Character Build()
    {
        return _character;
    }
}

// 사용 예
public class Game
{
    public void CreateHero()
    {
        HeroBuilder builder = new HeroBuilder();
        Character hero = builder.BuildWeapon("Sword")
                                 .BuildArmor("Shield")
                                 .BuildAbility("Invisibility")
                                 .Build();
    }
}

 

 

캐릭터가 가지는 공통적인 기능을 인터페이스로 구현한 다음 구체적인 캐릭터 클래스에서 원하는 옵션으로 조립할 수 있도록 단계를 구분한다.

728x90
반응형

+ Recent posts