Class(클래스)
Class(클래스)는 객체를 생성하기 위한 템플릿(Template)이다. 클래스를 사용하면 객체의 속성과 메소드를 묶어서 쉽게 관리할 수 있다.
TypeScript에서 클래스를 정의하는 방법은 다음과 같다.
class ClassName {
// 속성과 메소드 정의
}
위의 코드에서 ClassName은 클래스의 이름이다. 중괄호({}) 안에는 클래스가 가지는 속성(properties)과 메소드(methods)를 정의한다. 속성은 클래스의 데이터를 나타내며, 메소드는 클래스에서 수행하는 동작을 나타낸다.
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello(): void {
console.log(`안녕하세요, ${this.name}이다. ${this.age}살이다.`);
}
}
위의 코드에서 Person 클래스는 사람을 나타내는 클래스다. name과 age 속성과 sayHello() 메소드를 가지고 있다. constructor 메소드는 객체를 생성할 때 호출되며, 객체의 속성을 초기화한다. sayHello() 메소드는 name과 age 속성 값을 이용하여 인사말을 출력한다.
클래스는 객체를 생성하기 위한 템플릿이므로, 클래스를 사용하여 객체를 생성할 수 있다. 객체를 생성하는 방법은 다음과 같다.
const person = new Person("홍길동", 30);
person.sayHello(); // 안녕하세요, 홍길동이다. 30살이다.
위의 코드에서 new 키워드를 이용하여 Person 클래스의 인스턴스인 person 객체를 생성한다. name과 age 속성 값은 생성자(constructor)에 전달한다. person.sayHello()는 sayHello() 메소드를 호출하며, "안녕하세요, 홍길동이다. 30살이다."가 출력된다.
클래스는 객체를 생성하기 위한 템플릿일 뿐만 아니라, 상속을 이용하여 클래스 간에 관계를 정의할 수 있다. 또한, 클래스의 속성과 메소드에 접근 제한자를 사용하여 외부에서 접근할 수 없도록 제한할 수 있다.
Class Inheritance(클래스 상속)
Class Inheritance(클래스 상속)은 extends 키워드를 이용하여 구현할 수 있다. 상속은 부모 클래스에서 정의한 속성과 메소드를 자식 클래스에서 물려받아 사용하는 것을 의미한다.
class Animal {
constructor(public name: string, public age: number) {}
public makeSound(): void {
console.log("동물이 웁니다.");
}
}
class Dog extends Animal {
public makeSound(): void {
console.log("강아지가 멍멍 짖습니다.");
}
public wagTail(): void {
console.log("강아지가 꼬리를 흔듭니다.");
}
}
const animal = new Animal("동물", 5);
const dog = new Dog("강아지", 2);
console.log(animal.name); // 동물
console.log(dog.name); // 강아지
dog.makeSound(); // 강아지가 멍멍 짖습니다.
dog.wagTail(); // 강아지가 꼬리를 흔듭니다.
위의 코드에서 Animal 클래스는 동물을 나타내는 클래스이며, name과 age 속성과 makeSound() 메소드를 가지고 있다. Dog 클래스는 Animal 클래스를 상속받아서, makeSound() 메소드를 재정의하고 wagTail() 메소드를 추가로 가지고 있다.
animal 객체와 dog 객체는 각각 Animal 클래스와 Dog 클래스의 인스턴스다. animal 객체의 name 속성에는 "동물"이 할당되어 있고, dog 객체의 name 속성에는 "강아지"가 할당되어 있다. dog.makeSound()는 Dog 클래스에서 재정의한 makeSound() 메소드를 호출하며, "강아지가 멍멍 짖습니다."가 출력된다. dog.wagTail()은 Dog 클래스에서 추가한 wagTail() 메소드를 호출하며, "강아지가 꼬리를 흔듭니다."가 출력된다.
상속을 이용하면 코드의 중복을 줄일 수 있고, 객체의 관련성을 더욱 명확하게 표현할 수 있다. 하지만, 상속 구조가 복잡해지면 코드의 가독성이 떨어지고, 오류 발생의 위험이 증가할 수 있으므로 주의가 필요한다.
Access Modifiers(접근 제어자)
Access Modifiers(접근 제어자)는 클래스의 프로퍼티와 메소드에 대한 접근 권한을 지정하는 기능이다. 접근 제어자는 다음과 같이 세 가지가 있다.
public
public은 모든 곳에서 접근이 가능하다는 것을 의미한다. 클래스의 멤버를 선언할 때, 별도로 지정하지 않으면 public이 기본값이다.
class ClassName {
public property: string = "public property";
public method(): void {
console.log("public method");
}
}
위의 코드에서 property와 method는 모두 public으로 선언됐다. 따라서 클래스 외부에서도 접근이 가능하다.
private
private은 클래스 내부에서만 접근이 가능하다는 것을 의미한다.
class ClassName {
private property: string = "private property";
private method(): void {
console.log("private method");
}
}
위의 코드에서 property와 method는 모두 private으로 선언됐다. 따라서 클래스 외부에서는 접근이 불가능하다.
protected
protected는 클래스 내부와 상속 받은 클래스에서만 접근이 가능하다는 것을 의미한다.
class ParentClass {
protected property: string = "protected property";
}
class ChildClass extends ParentClass {
public method(): void {
console.log(this.property);
}
}
위의 코드에서 ParentClass의 property는 protected로 선언됐다. 따라서 ParentClass와 ChildClass에서 접근이 가능하지만, 클래스 외부에서는 접근이 불가능하다.
접근 제어자를 이용하여 클래스의 프로퍼티와 메소드에 대한 접근 권한을 명확하게 지정하면, 코드의 가독성과 유지보수성을 높일 수 있다. 또한 접근 제어자는 캡슐화를 구현하는 데 중요한 역할을 한다.
클래스의 종류
Singleton Class(싱글톤 클래스)
Singleton Class(싱글톤 클래스)는 애플리케이션에서 단 하나의 인스턴스만 생성하도록 하는 클래스이다. 이 패턴은 전역 변수를 사용하지 않고, 클래스 내부에서 유일한 인스턴스를 생성하도록 보장한다.
싱글톤 클래스는 보통 다음과 같은 형태로 구현된다.
class Singleton {
private static instance: Singleton;
private constructor() {}
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
위 예시 코드에서 싱글톤 클래스는 private 생성자를 가지고 있으며, 클래스 외부에서 인스턴스를 직접 생성할 수 없다. 대신 클래스 내부에서 유일한 인스턴스를 생성하고, 이를 반환하는 getInstance() static 메소드를 구현한다.
getInstance() 메소드에서는 싱글톤 클래스의 유일한 인스턴스를 생성한다. 이 메소드는 클래스 변수인 instance를 확인하여, 이미 인스턴스가 생성되어 있는 경우에는 새로운 인스턴스를 생성하지 않고 기존 인스턴스를 반환한다.
이렇게 하면 애플리케이션에서는 항상 단 하나의 Singleton 인스턴스만 사용할 수 있다.
싱글톤 클래스를 사용하면 애플리케이션에서 단 하나의 인스턴스만 생성하므로, 메모리 사용량을 줄일 수 있다. 또한 전역 변수를 사용하지 않고, 클래스 내부에서 유일한 인스턴스를 생성하므로, 코드의 결합도를 낮출 수 있다. 객체 간의 데이터 공유가 용이해지므로, 객체 간의 의존성을 줄일 수 있다.
하지만 싱글톤 클래스를 사용할 때는 몇 가지 주의점이 있다. 싱글톤 클래스의 인스턴스가 전역적으로 사용되므로, 다른 객체들과의 결합도가 높아질 수 있다. 또한 멀티스레드 환경에서 싱글톤 클래스를 사용할 때는 동기화 문제가 발생할 수 있으므로, 이에 대한 처리가 필요하다.
Generic Class(제네릭 클래스)
Generic Class(제네릭 클래스)는 클래스 내에서 사용되는 데이터 타입을 클래스를 정의할 때 결정하지 않고, 클래스를 사용하는 시점에 동적으로 결정할 수 있게 해주는 기능이다. 제네릭 클래스를 사용하면 클래스를 다양한 타입의 데이터에 대해 사용할 수 있으며, 코드의 재사용성을 높일 수 있다.
class ClassName<T> {
// ...
}
위의 코드에서 <T>는 Generic 타입 매개변수(Type parameter)다. T는 타입 변수(Type variable)로, 클래스 내에서 사용되는 타입을 표현한다. 타입 변수는 클래스 내부에서 타입을 결정하기 위해 사용된다.
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
console.log(numberStack.pop()); // 3
const stringStack = new Stack<string>();
stringStack.push("hello");
stringStack.push("world");
console.log(stringStack.pop()); // "world"
위의 코드에서 Stack<T> 클래스는 제네릭 클래스다. T는 타입 변수로, Stack 클래스 내에서 사용되는 타입을 표현한다. Stack<T> 클래스는 items 배열을 멤버 변수로 갖고 있으며, push()와 pop() 메소드를 구현한다.
numberStack와 stringStack 변수는 각각 Stack<number>와 Stack<string> 타입이다. numberStack 변수에는 숫자 타입의 데이터만 저장할 수 있으며, stringStack 변수에는 문자열 타입의 데이터만 저장할 수 있다. 이렇게 제네릭 클래스를 사용하면 다양한 타입의 데이터에 대해 클래스를 재사용할 수 있다.
Abstract Class(추상 클래스)
Abstract Class(추상 클래스)는 하나 이상의 추상 메소드(abstract method)를 가지는 클래스이다.추상 메소드는 구현(implement)되지 않은 메소드로, 상속한 클래스에서 반드시 구현해야 한다.
abstract class ClassName {
abstract methodName(): returnType;
// ...
}
위의 코드에서 abstract 키워드를 이용하여 추상 클래스를 정의한다. 추상 메소드는 abstract 키워드를 메소드 앞에 붙여서 정의한다. 추상 메소드는 구현되지 않았으므로, 메소드 본문(body)이 없고 반환 값(return value)도 지정하지 않는다.
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log("동물이 움직이다.");
}
}
위의 코드에서 Animal 클래스는 추상 클래스다. makeSound() 메소드는 추상 메소드로, 구현되지 않았다. move() 메소드는 일반 메소드로, 구현되어 있다.
추상 클래스는 객체를 직접 생성할 수 없다. 추상 클래스를 상속받은 자식 클래스에서 추상 메소드를 구현해야만 객체를 생성할 수 있다.
class Cat extends Animal {
makeSound(): void {
console.log("냐옹");
}
}
const cat = new Cat();
cat.makeSound(); // 냐옹
cat.move(); // 동물이 움직이다.
위의 코드에서 Cat 클래스는 Animal 클래스를 상속받았다. makeSound() 메소드는 구현되어 있으므로, Cat 클래스에서는 추상 클래스인 Animal 클래스의 추상 메소드를 모두 구현했다. cat.makeSound()는 Cat 클래스에서 구현한 makeSound() 메소드를 호출하며, "냐옹"이 출력된다. cat.move()는 Animal 클래스에서 상속받은 move() 메소드를 호출하며, "동물이 움직이다."가 출력된다.
추상 클래스는 자식 클래스에서 공통된 메소드를 정의할 때 사용할 수 있다. 추상 클래스를 상속받은 자식 클래스는 추상 메소드를 구현해야 하므로, 메소드 구현을 강제할 수 있다. 이를 이용하여 코드의 일관성과 유지보수성을 높일 수 있다.