📌 Class (클래스)
1. 클래스란?
2. 클래스의 구성 요소
3. 객체란?
4. 클래스 및 객체 정의 방법
class
키워드를 통해 정의한다. new
키워드를 사용하여 객체를 생성할 수 있다.class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`안녕하세요! 제 이름은 ${this.name}이고, 나이는 ${this.age}살입니다.`);
}
}
const person = new Person('Spartan', 30);
person.sayHello();
5. 생성자(constructor)
constructor
이라는 이름으로 정의된다.6. 클래스 접근 제한자
클래스에서는 속성과 메서드에 접근 제한자를 사용해 접근을 제한할 수 있다.
public
private
protected
class Person {
private name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public sayHello() {
console.log(`안녕하세요! 제 이름은 ${this.name}이고, 나이는 ${this.age}살입니다.`);
}
}
📌 Inheritance(상속)
1. 상속이란?
extends
키워드를 사용한다.class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
makeSound() {
console.log('동물 소리~');
}
}
class Dog extends Animal {
age: number;
constructor(name: string) {
super(name);
this.age = 5;
}
makeSound() {
console.log('멍멍!'); // 부모의 makeSound 동작과 다름
}
eat() { // Dog 클래스만의 새로운 함수 정의
console.log('강아지가 사료를 먹습니다.');
}
}
class Cat extends Animal { // Animal과 다를게 하나도 없음
}
const dog = new Dog('누렁이');
dog.makeSound(); // 출력: 멍멍!
const cat = new Cat('야옹이');
cat.makeSound(); // 출력: 동물 소리~
Animal
을 부모 클래스, Dog
, Cat
를 자식 클래스라고 한다.super
키워드는 자식 클래스가 부모 클래스를 참조하는데 사용하는 키워드이다.Cat
클래스처럼 자식 클래스가 부모 클래스의 생성자나 메서드를 그대로 사용할 경우 자식 클래스에서는 생성자나 매서드를 다시 작성할 필요가 없다.Dog
클래스는 부모의 makeSound 함수의 동작을 새롭게 정의하고 있는데, 이를 오버라이팅
이라고 한다.Animal
은 Dog
, Cat
의 슈퍼타입이고, Dog
, Cat
은 Animal
의 서브타입이다.2. 서브타입과 슈퍼타입
서브타입 : 두 개의 타입 A와 B가 있고 B가 A의 서브타입이면 A가 필요한 곳에는 어디든 B를 안전하게 사용할 수 있다.
슈퍼타입 : 두 개의 타입 A와 B가 있고 B가 A의 슈퍼타입이면 B가 필요한 곳에는 어디든 A를 안전하게 사용할 수 있다.
any
는 모든 것의 슈퍼타입이다.
3. upcasting과 downcasting
1) upcasting
Dog
, Cat
등 다양한 동물을 인자로 받을 수 있는 함수를 만들고 싶다면, union으로 새로운 타입을 만들어 해당 타입의 객체를 받는 것이 아니라, Animal
타입의 객체를 받으면 모두를 받을 수 있다.let dog: Dog = new Dog('또순이');
let animal: Animal = dog; // upcasting 발동!
animal.eat(); // 에러. 슈퍼타입(Animal)으로 변환이 되어 eat 메서드를 호출할 수 없음
2) downcasting
as
키워드로 명시적 타입 변환을 해줘야 한다.Dog
와 같은 서브타입 메서드를 사용해야 될 때 변신 해야 될 수 있다.let animal: Animal;
animal = new Dog('또순이');
let realDog: Dog = animal as Dog;
realDog.eat(); // 서브타입(Dog)로 변환이 되었기 때문에 eat 메서드를 호출할 수 있음
📌 추상 클래스
1. 추상 클래스란?
2. 사용 방법
abstract
키워드를 사용하여 정의한다.abstract class Shape {
abstract getArea(): number; // 추상 함수 정의!!!
printArea() {
console.log(`도형 넓이: ${this.getArea()}`);
}
}
class Circle extends Shape {
radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
getArea(): number { // 원의 넓이를 구하는 공식은 파이 X 반지름 X 반지름
return Math.PI * this.radius * this.radius;
}
}
class Rectangle extends Shape {
width: number;
height: number;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}
getArea(): number { // 사각형의 넓이를 구하는 공식은 가로 X 세로
return this.width * this.height;
}
}
const circle = new Circle(5);
circle.printArea();
const rectangle = new Rectangle(4, 6);
rectangle.printArea();
abstract getArea(): number;
가 바로 추상 함수이다.Circle
, Rectangle
)는 반드시 getArea
함수를 구현해야 한다.📌 인터페이스
1. 인터페이스란?
2. 추상 클래스 vs 인터페이스
구현부 제공 여부
: 추상 클래스는 클래스의 기본 구현을 제공하지만, 인터페이스는 객체의 구조만을 정의하고 기본 구현을 제공하지 않는다.상속 메커니즘
: 추상 클래스는 단일 상속만 지원하지만, 인터페이스는 다중 상속을 지원한다. 즉, 하나의 클래스는 여러 인터페이스를 구현할 수 있다.구현 메커니즘
: 추상 클래스를 상속받은 자식 클래스는 반드시 추상 함수를 구현해야 하고, 인터페이스를 구현하는 클래스는 인터페이스에 정의된 모든 메서드를 전부 구현해야 한다.💡 기본 구현을 제공하고 상속을 통해 확장하는데 초점을 맞추고 싶다면 추상 클래스
를, 객체가 완벽하게 특정 구조를 준수하도록 강제하고 싶다면 인터페이스
를 사용하는 것이 적합하다.
📌 객체 지향 설계 원칙 - S.O.L.I.D
1. S(SRP. 단일 책임 원칙)
class UserService {
constructor(private db: Database) {}
getUser(id: number): User {
// 사용자 조회 로직
return this.db.findUser(id);
}
saveUser(user: User): void {
// 사용자 저장 로직
this.db.saveUser(user);
}
}
class EmailService {
// 이메일 관련된 기능은 이메일 서비스에서 총괄하는게 맞음
// 다른 서비스에서 이메일 관련된 기능을 쓴다는 것은 영역을 침범하는 것
sendWelcomeEmail(user: User): void {
// 이메일 전송 로직
console.log(`Sending welcome email to ${user.email}`);
}
}
2. O(OCP. 개방 폐쇄 원칙)
3. L(LSP. 리스코프 치환 원칙)
abstract class Bird {
abstract move(): void;
}
class FlyingBird extends Bird {
move() {
console.log("펄럭펄럭~");
}
}
class NonFlyingBird extends Bird {
move() {
console.log("뚜벅뚜벅!");
}
}
class Penguin extends NonFlyingBird {} // 이제 위배되는 것은 아무것도 없음
4. I(ISP. 인터페이스 분리 원칙)
5. D(DIP. 의존성 역전 원칙)
interface MyStorage {
save(data: string): void;
}
class MyLocalStorage implements MyStorage {
save(data: string): void {
console.log(`로컬에 저장: ${data}`);
}
}
class MyCloudStorage implements MyStorage {
save(data: string): void {
console.log(`클라우드에 저장: ${data}`);
}
}
class Database {
// 상위 수준 모듈인 MyStorage 타입을 의존
// 여기서 MyLocalStorage, MyCloudStorage 같은 하위 수준 모듈에 의존하지 않는게 핵심
constructor(private storage: MyStorage) {}
saveData(data: string): void {
this.storage.save(data);
}
}
const myLocalStorage = new MyLocalStorage();
const myCloudStorage = new MyCloudStorage();
const myLocalDatabase = new Database(myLocalStorage);
const myCloudDatabase = new Database(myCloudStorage);
myLocalDatabase.saveData("로컬 데이터");
myCloudDatabase.saveData("클라우드 데이터");