절차 지향
: 정의된 순서대로(절차적으로) 함수가 하나씩 호출됨
- 하나를 수정하기 위해서 전체적인 어플리케이션을 이해해야함
- 수정했을때 side Effect가 발생할 수 있음
- 한눈에 이해하기 어렵다
Coffee Maker 예제)
{
/**
* 절차지향적 프로그래밍
*/
type CoffeeCup = {
shots: number;
hasMilk: boolean;
};
const BEANS_GRAMM_PER_SHOT: number = 7;
let coffeebeans: number = 0;
function makeCoffee(shots: number): CoffeeCup {
// 커피 콩이 부족하다면
if (coffeebeans < shots * BEANS_GRAMM_PER_SHOT) {
throw new Error("Not enough coffee beans!");
}
coffeebeans -= shots * BEANS_GRAMM_PER_SHOT;
return {
shots,
hasMilk: false,
};
}
// 커피 콩 충전
coffeebeans += 3 * BEANS_GRAMM_PER_SHOT
const coffee = makeCoffee(2);
console.log(coffee)
}
객체 지향 (OOP)
: 서로 관련있는 데이터와 함수를 하나의 객체로 정의해서 사용
- 관련있는 객체만 수정
- 재사용 가능
- 확장성
=> 생산성, 퀄리티, 빠른 속도의 개발
Class
객체는 대부분 class를 이용해서 정의! class는 템플릿같은 존재,,
객체는 class에 실제로 데이터를 넣어서 만든 instance
Coffee Maker 예제)
{
/**
* 객체지향 프로그래밍
*/
type CoffeeCup = {
shots: number;
hasMilk: boolean;
};
class CoffeeMaker {
BEANS_GRAMM_PER_SHOT: number = 7;
coffeebeans: number = 0;
// 인스턴스를 만들 때 호출되는 함수
constructor(coffeeBeans: number) {
this.coffeebeans = coffeeBeans; // 커피 콩 충전
}
makeCoffee(shots: number): CoffeeCup {
// 커피 콩이 부족하다면
if (this.coffeebeans < shots * this.BEANS_GRAMM_PER_SHOT) {
throw new Error("Not enough coffee beans!");
}
this.coffeebeans -= shots * this.BEANS_GRAMM_PER_SHOT;
return {
shots,
hasMilk: false,
};
}
}
// 인스턴스 생성
const maker = new CoffeeMaker(32);
console.log(maker)
const maker2 = new CoffeeMaker(25);
console.log(maker2)
}
Static
위의 코드에서 BEANS_GRAMM_PER_SHOT 이라는 멤버 변수는 어떤 객체에서도 같은 값을 가진다.
=> 객체를 생성할때마다 BEANS_GRAMM_PER_SHOT도 새로 만들어지기 때문에 메모리의 낭비가 발생!
이때, static 키워드를 붙여주면 object가 아닌 class와 연결되므로 object마다 생성되지 않는다.
static BEANS_GRAMM_PER_SHOT: number = 7; // class level
coffeebeans: number = 0; // instance level
=> 클래스 자체에 연결되어있기 때문에 this가 아닌 클래스 이름을 붙여주어야 한다.
CoffeeMaker.BEANS_GRAMM_PER_SHOT
{
/**
* 객체지향 프로그래밍
*/
type CoffeeCup = {
shots: number;
hasMilk: boolean;
};
class CoffeeMaker {
static BEANS_GRAMM_PER_SHOT: number = 7; // class level
coffeebeans: number = 0; // instance level
// 인스턴스를 만들 때 호출되는 함수
constructor(coffeeBeans: number) {
this.coffeebeans = coffeeBeans; // 커피 콩 충전
}
makeCoffee(shots: number): CoffeeCup {
// 커피 콩이 부족하다면
if (this.coffeebeans < shots * CoffeeMaker.BEANS_GRAMM_PER_SHOT) {
throw new Error("Not enough coffee beans!");
}
this.coffeebeans -= shots * CoffeeMaker.BEANS_GRAMM_PER_SHOT;
return {
shots,
hasMilk: false,
};
}
}
// 인스턴스 생성
const maker = new CoffeeMaker(32);
console.log(maker);
const maker2 = new CoffeeMaker(25);
console.log(maker2);
}
=> 클래스 레벨의 변수는 객체에 포함되어 있지 않음
함수에서도 적용 가능
// 클래스 내부의 어떤 속성도 팔요로 하지 않기 떄문에 static으로 함수 선언
static makeMachine(coffeeBeans:number): CoffeeMaker {
return new CoffeeMaker(coffeeBeans);
}
const maker3 = CoffeeMaker.makeMachine(3);
console.log(maker3)
객체지향의 원칙
1. 캡슐화
: 서로 관련있는 데이터와 함수를 한 객체 안에 담아두는 것, 외부에는 내용을 보여주지 않는 것
-> 내부 상태를 외부에서 변경할 수는 없지만 외부에서 하는 행동으로 인해 내부 상태가 변경될 수는 있음
2. 추상화
: 내부의 복잡한 기능을 알고있지 않아도, 외부에서 간단한 인터페이스를 통해 기능을 이용할 수 있는것
3. 상속 (IS-A 관계)
: 한번 잘 정의해둔 클래스를 상속받아 재정의하여 사용할 수 있음
4. 다형성
: 하나의 객체가 여러가지 타입을 가질 수 있는 것
1. Encapsulation (캡슐화)
위의 CoffeeMaker 예제에서 클래스 외부에서 커피콩을 음수로 충전할 수있다.
=> 유효하지 않은 값도 충전할 수 있기 때문에 외부에서 값을 변경할 수 없게 해야 함
const maker = CoffeeMaker.makeMachine(3);
maker.coffeebeans = 3;
maker.coffeebeans = -33; // 유효하지 않음
public | 클래스 외부에서 내부 접근 가능, default 값 |
private | 클래스 외부에서 내부의 변수에 접근하지 못함 |
protected | 상속 시에 외부에서는 접근할 수 없지만 자식 클래스에서는 접근 가능 |
lass CoffeeMaker {
private static BEANS_GRAMM_PER_SHOT: number = 7; // class level
// 자식 클래스에서는 접근 가능
protected coffeebeans: number = 0; // instance level
// 클래스 내부의 어떤 속성도 팔요로 하지 않기 떄문에 static으로 함수 선언
static makeMachine(coffeeBeans: number): CoffeeMaker {
return new CoffeeMaker(coffeeBeans);
}
}
외부에서 생성자를 직접 사용하는 것을 금지하고 싶다면?
constructor 메소드를 private으로 선언하여 static method를 사용하도록 권장
// 인스턴스를 만들 때 호출되는 함수
private constructor(coffeeBeans: number) {
this.coffeebeans = coffeeBeans; // 커피 콩 충전
}
// 클래스 내부의 어떤 속성도 팔요로 하지 않기 떄문에 static으로 함수 선언
static makeMachine(coffeeBeans: number): CoffeeMaker {
return new CoffeeMaker(coffeeBeans);
}
* Getter & Setter
Getter
class User {
firstName: string;
lastName: string;
fullName: string;
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
this.fullName = `${firstName} ${lastName}`;
}
}
const user = new User("Yuna", "Kim");
console.log(user.fullName);
user.firstName = "Hey";
// fullName은 한번 할당되어진대로 고정
console.log(user.fullName);
=> user의 firstName을 수정해보았다.
=> fullName이라는 멤버변수는 객체가 생성될 때 할당 된 대로 고정된다.
이때, get을 사용해보자
class User {
firstName: string;
lastName: string;
// fullName이 불리는 시점에 계산된 값을 반환해준다.
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}
}
const user = new User("Yuna", "Kim");
console.log(user.fullName);
user.firstName = "Hey";
console.log(user.fullName);
=> 함수 형태이지만 접근할 때는 멤버변수와 동일하게 접근해야한다.
=> fullName이 불리는 시점에 계산 된 값을 반환한다.
Getter & Setter
class User {
// fullName이 불리는 시점에 계산된 값을 반환해준다.
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
private internalAge = 4;
// Getter
get age(): number {
return this.internalAge;
}
// Setter - 값을 할당
set age(num: number) {
if (num < 0) {
throw new Error("유효하지 않은 값 입니다!");
}
this.internalAge = num;
}
// 생성자에 접근제어자를 설정하면 자동으로 멤버변수가 된다.
constructor(private firstName: string, private lastName: string) {}
}
const user = new User("Yuna", "Kim");
user.age = 6;
console.log(user.age);
}
2. Abstraction (추상화)
: 클래스를 이용하는 사람에게 정말 필요한 함수만 노출
{
/**
* 추상화
*/
type CoffeeCup = {
shots: number;
hasMilk: boolean;
};
class CoffeeMaker {
private static BEANS_GRAMM_PER_SHOT: number = 7; // class level
private coffeebeans: number = 0; // instance level
// 인스턴스를 만들 때 호출되는 함수
private constructor(coffeeBeans: number) {
this.coffeebeans = coffeeBeans; // 커피 콩 충전
}
// 클래스 내부의 어떤 속성도 팔요로 하지 않기 떄문에 static으로 함수 선언
static makeMachine(coffeeBeans: number): CoffeeMaker {
return new CoffeeMaker(coffeeBeans);
}
fillCoffeeBeans(beans: number) {
if (beans < 0) {
throw new Error("value for beans should be greater than 0");
}
this.coffeebeans += beans;
}
private grindBeans(shots: number) {
console.log(`grinding beans for ${shots}`);
if (this.coffeebeans < shots * CoffeeMaker.BEANS_GRAMM_PER_SHOT) {
throw new Error("Not enough coffee beans!");
}
this.coffeebeans -= shots * CoffeeMaker.BEANS_GRAMM_PER_SHOT;
}
private preheat(): void {
console.log("heating up,,,");
}
private extract(shots: number): CoffeeCup {
console.log(`Pulling ${shots} shots...`);
return {
shots,
hasMilk: false,
};
}
makeCoffee(shots: number): CoffeeCup {
this.grindBeans(shots);
this.preheat();
return this.extract(shots);
}
}
const maker = CoffeeMaker.makeMachine(3);
maker.fillCoffeeBeans(32);
}
외부에서 fillCoffeeBeans와 makeCoffee에만 접근할 수 있게 하기 위해서 내부에 있는 다른 함수에 private을 붙여주었다.
* Interface 이용하는 방법
: 추상화를 더 극대화해서 사용할 수 있다.
interface를 클래스에 implement 하여 해당 클래스의 조상을 설정함과 동시에 사용할 함수, 변수를 제한할 수 있음
// 각각의 인터페이스에서 사용할 메소드, 변수를 정의함
interface CoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
}
interface CommercialCoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
fillCoffeeBeans(beans: number): void;
clean(): void;
}
// 인스턴스는 각각 CoffeeMaker, CommercialCoffeeMaker를 implment함
class CoffeeMachine implements CoffeeMaker, CommercialCoffeeMaker {
private static BEANS_GRAMM_PER_SHOT: number = 7; // class level
private coffeeBeans: number = 0; // instance level
// 인스턴스를 만들 때 호출되는 함수
private constructor(coffeeBeans: number) {
this.coffeeBeans = coffeeBeans; // 커피 콩 충전
}
// 클래스 내부의 어떤 속성도 팔요로 하지 않기 떄문에 static으로 함수 선언
static makeMachine(coffeeBeans: number): CoffeeMachine {
return new CoffeeMachine(coffeeBeans);
}
fillCoffeeBeans(beans: number) {
if (beans < 0) {
throw new Error("value for beans should be greater than 0");
}
this.coffeeBeans += beans;
}
private grindBeans(shots: number) {
console.log(`grinding beans for ${shots}`);
if (this.coffeeBeans < shots * CoffeeMachine.BEANS_GRAMM_PER_SHOT) {
throw new Error("Not enough coffee beans!");
}
this.coffeeBeans -= shots * CoffeeMachine.BEANS_GRAMM_PER_SHOT;
}
private preheat(): void {
console.log("heating up,,,");
}
private extract(shots: number): CoffeeCup {
console.log(`Pulling ${shots} shots...`);
return {
shots,
hasMilk: false,
};
}
clean() {
console.log("Cleaning the Machine");
}
makeCoffee(shots: number): CoffeeCup {
this.grindBeans(shots);
this.preheat();
return this.extract(shots);
}
}
각각의 인터페이스로 객체 생성해보기
class AmateurUser {
constructor(private machine: CoffeeMaker) {}
makeCoffee() {
const coffee = this.machine.makeCoffee(2);
console.log(coffee);
}
}
class ProBarista {
constructor(private machine: CommercialCoffeeMaker) {}
makeCoffee() {
const coffee = this.machine.makeCoffee(2);
console.log(coffee);
this.machine.fillCoffeeBeans(45);
this.machine.clean();
}
}
const maker: CoffeeMachine = CoffeeMachine.makeMachine(32);
// 같은 maker를 인자로 받지만 각각의 인터페이스에 규약된 함수만 사용할 수 있다.
const amateur = new AmateurUser(maker);
amateur.makeCoffee();
console.log('------------------------')
const pro = new ProBarista(maker);
pro.makeCoffee();
3. Inheritance (상속)
: 부모 클래스를 상속받아 공통적인 부분은 그대로 재사용 하거나 overwriting 하여 사용
[부모 클래스]
class CoffeeMachine implements CoffeeMaker {
private static BEANS_GRAMM_PER_SHOT: number = 7; // class level
private coffeeBeans: number = 0; // instance level
// 인스턴스를 만들 때 호출되는 함수
public constructor(coffeeBeans: number) {
this.coffeeBeans = coffeeBeans; // 커피 콩 충전
}
// 클래스 내부의 어떤 속성도 팔요로 하지 않기 떄문에 static으로 함수 선언
static makeMachine(coffeeBeans: number): CoffeeMachine {
return new CoffeeMachine(coffeeBeans);
}
fillCoffeeBeans(beans: number) {
if (beans < 0) {
throw new Error("value for beans should be greater than 0");
}
this.coffeeBeans += beans;
}
private grindBeans(shots: number) {
console.log(`grinding beans for ${shots}`);
if (this.coffeeBeans < shots * CoffeeMachine.BEANS_GRAMM_PER_SHOT) {
throw new Error("Not enough coffee beans!");
}
this.coffeeBeans -= shots * CoffeeMachine.BEANS_GRAMM_PER_SHOT;
}
private preheat(): void {
console.log("heating up,,,");
}
private extract(shots: number): CoffeeCup {
console.log(`Pulling ${shots} shots...`);
return {
shots,
hasMilk: false,
};
}
clean() {
console.log("Cleaning the Machine");
}
makeCoffee(shots: number): CoffeeCup {
this.grindBeans(shots);
this.preheat();
return this.extract(shots);
}
}
[자식 클래스]
=> extends를 이용하여 상속
=> 변경할 부분은 오버라이팅 하여 재사용함 (여기서는 hasMik를 true로 변경해주었음)
class CaffeLatteMachine extends CoffeeMachine {
constructor(beans: number, public readonly serialNumber: string) {
super(beans);
// ...code
}
private steamMilk(): void {
console.log("Steaming some milk...");
}
makeCoffee(shots: number): CoffeeCup {
const coffee = super.makeCoffee(shots);
this.steamMilk();
return { ...coffee, hasMilk: true }; // 우유만 부어줘,,,
}
}
※ 자식클래스에서 생성자(constructor)를 새로 작성하는 경우에는, super()를 호출해주어야함
// 자식 클래스
class CaffeLatteMachine extends CoffeeMachine {
constructor(beans: number, public readonly serialNumber: string) {
// 부모 클래스 생성시 필요한 데이터도 가져와야함
super(beans);
// ...code
}
private steamMilk(): void {
console.log("Steaming some milk...");
}
makeCoffee(shots: number): CoffeeCup {
const coffee = super.makeCoffee(shots);
this.steamMilk();
return { ...coffee, hasMilk: true }; // 우유만 부어줘,,,
}
}
...
const latteMachine = new CaffeLatteMachine(23, 'sss');
4. Polemorphism (다형성)
우선, 설탕을 추가하는 커피 머신을 만들어보자
/ CoffeeMachine을 상속하는 클래스
class SweetCoffeeMaker extends CoffeeMachine {
private addSugar(): void {
console.log("Adding some sugar...");
}
makeCoffee(shots: number): CoffeeCup {
const coffee = super.makeCoffee(shots);
this.addSugar();
return { ...coffee, hasSugar: true }; // 우유만 부어줘,,,
}
}
내부적으로 구현된 다양한 클래스들이 한가지 인터페이스를 구현하거나 같은 부모로부터 상속받았을때, 어떤 클래스인지 구분하지 않고 공통된 함수(makeCoffee)를 호출할 수 있다!
모든 machine이 makeCoffee라는 공통된 함수를 호출할 수 있는 것은 ,
machines라는 배열의 타입이 CoffeMachine[] 이기 때문이다
CoffeeMachine은 CoffeeMaker를 구현하므로,
이렇게도 명시할 수 있게되는데, 그럼 각 machine들은 인터페이스에 작성된 대로 makeCoffee라는 함수에만 접근할 수 있게 된다
interface CoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
}
...
machines.forEach((machine) => {
console.log("--------------------------");
machine.makeCoffee(1);
});
만날때 나 준다고 딸기 한박스 들고오는 친구 있으면 성공한 인생 아니냐구요,, 감사해~
'Web > TypeScript' 카테고리의 다른 글
[TypeScript] 제네릭 (Generics) (0) | 2023.03.01 |
---|---|
[TypeScript] Composition (0) | 2023.02.25 |
[TypeScript] 기본 타입 #2 (0) | 2023.02.22 |
[TypeScript] 기본 타입 #1 (0) | 2023.02.21 |
[TypeScript] TypeScript 시작하기 (0) | 2023.02.21 |
댓글