23.07.28
5-1 클래스
그렇다면 여기서 속성과 메서드란 ?
속성 : 객체의 성질을 결정하는 것
ex. 팥 붕어빵 - 팥이란 속성을 가짐, 슈크림 붕어빵 - 슈크림이란 속성을 가짐
메서드 : 객체의 성질을 변화시키거나 객체에서 제공하는 기능들을 사용
ex. 붕어빵 주인은 붕어빵을 팥붕에서 슈붕으로 전환할 수 있음
손님들은 붕어빵의 가격을 알 수 있음
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();
접근 제한자가 선언이 안되어있다면 default는 public
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}살입니다.);
}
}
5-2 ~ 5-3 상속
사용법 => class 자식 클래스 extends 기존 클래스 !!!
super : 자식 클래스가 부모 클래스를 참조하는데 사용하는 키워드
즉, 자식 클래스에서 생성자를 정의할 때 부모 클래스의 생성자를 호출해야 하는데 이 때 씁니다!
자식 클래스가 부모 클래스의 생성자나 메서드를 그대로 사용하고 싶다면 자식 클래스에선 다시 작성하지 않아도 됨! → Cat 클래스를 보세요!
=> 아래에서는 Dog 클래스에 age라는 속성이 추가되었기 때문에 super 키워드를 사용함
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(); // 출력: 동물 소리~
let dog: Dog = new Dog('또순이');
let animal: Animal = dog; // upcasting 발동!
animal.eat(); // 에러. 슈퍼타입(Animal)으로 변환이 되어 eat 메서드를 호출할 수 없어요!
그렇다면 upcasting을 ꖶዞ 사용해야할까 ?
let animal: Animal;
animal = new Dog('또순이');
let realDog: Dog = animal as Dog;
realDog.eat(); // 서브타입(Dog)로 변환이 되었기 때문에 eat 메서드를 호출
5-4 추상클래스
(목적)
=> 즉, 추상 클래스를 상속 받은 자식 클래스들은 반드시 추상 함수를 구현해야함 , 그렇지 않으면 오류 !
5-5 인터페이스 interface
구현부 제공 여부
인 : ‘객체’의 구조만을 정의, 기분 구현 제공 안함
추 : ‘클래스’의 기본 구현 제공
상속 메커니즘
인 : 다중 상속(보다는 인터페이스)을 지원, (=> 즉 하나의 클래스에서 여러 개의 인터페이스 구현 가능)
추 : 단일 상속을 지원
구현 메커니즘
인 : 인터페이스 를 구현하는 클래스는 인터페이스 정의된 모든 메서드를 전부 구현
추 : 추상 클래스를 상속받은 자식 클래스는 반드시 추상 함수를 구현
인터페이스 : 객체가 완벽하게 특정 구조를 준수하도록 강제하고자 할 때 사용하면 적합
추상 클래스 : 기본 구현을 제공하고 상속을 통해 확장하고자 할 때 사용하면 적합
5-6 객체 지향 설계원칙 - S.O.L.I.D
객체 지향 설계를 할 때는 SOLID 원칙을 따르는게 필수 => 이렇게 설계하게 되면 프로그램이 유연, 확장 가능, 이해하기 쉬운 구조
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});
}
}
논리적으로 제대로 된 구조인가 !!!!
올바르게 성립이 되는가 !!!
reasonable한가 !!!
abstract class Bird {
abstract move(): void;
}
class FlyingBird extends Bird {
move() {
console.log("펄럭펄럭~");
}
}
class NonFlyingBird extends Bird {
move() {
console.log("뚜벅뚜벅!");
}
}
class Penguin extends NonFlyingBird {} // 이제 위배되는 것은 아무것도 없네요!
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("클라우드 데이터");
5-9 ~ 5-11 실습 !
interface => initialState
✅ 제대로 이해했는지 체크해봅시다.