# OOP (Object Oriented Programming)
객체 지향적 프로그래밍은 기존의 순차적인 프로그래밍을 데이터의 처리 과정을 보다 다양한 방식으로 수행할 수 있도록 변화하였다. 이로서 데이터와 기능이 별개가 아닌 하나의 묶음으로 처리 될 수 있도록 프로그래밍이 가능하게 되었다.
최근 엘리님이 진행하시는 dreamcoding typescript 편을 수강하면서 OOP에 대한 개념을 확실한 예제와 함께 정리할 수 있었는데, 예시 코드는 드림코딩에 엘리님의 진행하시는 강좌를 기반으로 작성 되었음을 알립니다.
타입스크립트에 대한 개념을 잡아 줄수 있는 수업으로 도움이 많이 되었습니다.
# Encapsulation (캡슐화)
- 데이터와 기능을 하나의 단위로 묶는 것
- 은닉 (hiding) : 구현을 숨기고, 동작은 노출시킴
- 느슨한 결합 (Loose Coupling)에 유리 : 언제든 구현을 수정할 수 있음
- 이는 코드가 복잡하지 않게 만들어주고, 재사용성을 높입니다.
type CoffeeCup = {
shots: number;
hasMilk: boolean;
};
// public 일반적
// private 외부에서 볼 수 없고 접근 할 수 없음
// protected 상속시 자식클레스에서만 접근 할 수 있음
class CoffeeMaker {
private static BEANS_GRAMM_PER_SHOT: number = 8; // class level 맴버 변수뿐 아니라 함수에서도 호출 가능
private coffeeBeans: number = 0; // instance level 맴버 변수
private constructor(beans: number) {
this.coffeeBeans = beans;
}
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;
}
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 = CoffeeMaker.makeMachine(32);
maker.fillCoffeeBeans(20);
class User {
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
private internalAge = 4;
get age(): number {
return this.internalAge;
}
set age(num: number) {
if (num < 0) {
//유효성 검사도 할 수 있음.
throw new Error("Age is should greater than 0");
}
this.internalAge = num;
}
constructor(private firstName: string, private lastName: string) {}
}
const user = new User("Steve", "Jobs");
user.age = 6;
console.log(user.fullName);
# inheritance (상속)
class 여기를 살펴보자
type CoffeeCup = {
shots: number;
hasMilk: boolean;
};
interface CoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
} //계약서, 명세서 같은 녀석
class CoffeeMachine implements CoffeeMaker {
private static BEANS_GRAMM_PER_SHOT: number = 8; // class level 맴버 변수뿐 아니라 함수에서도 호출 가능
private coffeeBeans: number = 0; // instance level 맴버 변수
constructor(beans: number) {
this.coffeeBeans = beans;
}
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;
}
clean() {
console.log("cleaning the machine...");
}
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,
};
}
makeCoffee(shots: number): CoffeeCup {
this.grindBeans(shots);
this.preheat();
return this.extract(shots);
}
}
class CaffeLatteMachine extends CoffeeMachine {
constructor(beans: number, public readonly serialNumber: string) {
super(beans);
}
private steamMilk(): void {
console.log("steaming some milk");
}
makeCoffee(shots: number): CoffeeCup {
const coffee = super.makeCoffee(shots);
this.steamMilk();
return {
...coffee,
hasMilk: true,
};
}
}
const machine = new CoffeeMachine(23);
const latteMachine = new CaffeLatteMachine(23, "SSSSS");
const coffee = latteMachine.makeCoffee(2);
console.log(coffee);
console.log(latteMachine.serialNumber);
# Abstraction (추상화)
복잡한 과정은 안으로 숨기고 기능적인 부분만 노출할 수 있다.
- 인터페이스의 단순화
- 메소드와 속성만 정의한 것을 인터페이스라 한다.
- 변화에 대한 영향을 최소화 할 수 있다.
type CoffeeCup = {
shots: number;
hasMilk: boolean;
};
interface CoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
} //계약서, 명세서 같은 녀석
interface CommercialCoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
fillCoffeeBeans(beans: number): void;
clean(): void;
} //계약서, 명세서 같은 녀석
class CoffeeMachine implements CoffeeMaker, CommercialCoffeeMaker {
private static BEANS_GRAMM_PER_SHOT: number = 8; // class level 맴버 변수뿐 아니라 함수에서도 호출 가능
private coffeeBeans: number = 0; // instance level 맴버 변수
private constructor(beans: number) {
this.coffeeBeans = beans;
}
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;
}
clean() {
console.log("cleaning the machine...");
}
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,
};
}
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);
const amateur = new AmateurUser(maker);
const pro = new ProBarista(maker);
amateur.makeCoffee();
pro.makeCoffee();
# Polymorphism (다향성)
- 하나의 클래스를 통해 같은 기능이 조금씩 다르게 적용되도록 할 수 있다.
- 자동차의 색깔을 바꾼다던지 종류를 바꾼다던지 하는식
type CoffeeCup = {
shots: number;
hasMilk: boolean;
hasSugar?: boolean;
};
interface CoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
} //계약서, 명세서 같은 녀석
class CoffeeMachine implements CoffeeMaker {
private static BEANS_GRAMM_PER_SHOT: number = 8; // class level 맴버 변수뿐 아니라 함수에서도 호출 가능
private coffeeBeans: number = 0; // instance level 맴버 변수
constructor(beans: number) {
this.coffeeBeans = beans;
}
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;
}
clean() {
console.log("cleaning the machine...");
}
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,
};
}
makeCoffee(shots: number): CoffeeCup {
this.grindBeans(shots);
this.preheat();
return this.extract(shots);
}
}
class CaffeLatteMachine extends CoffeeMachine {
constructor(beans: number, public readonly serialNumber: string) {
super(beans);
}
private steamMilk(): void {
console.log("steaming some milk");
}
makeCoffee(shots: number): CoffeeCup {
const coffee = super.makeCoffee(shots);
this.steamMilk();
return {
...coffee,
hasMilk: true,
};
}
}
class SweetCoffeeMaker extends CoffeeMachine {
makeCoffee(shots: number): CoffeeCup {
const coffee = super.makeCoffee(shots);
return {
...coffee,
hasSugar: true,
};
}
}
const machines: CoffeeMaker[] = [
new CoffeeMachine(16),
new CaffeLatteMachine(16, "SSSSSS"),
new SweetCoffeeMaker(15),
];
machines.forEach((machine) => {
console.log("===============================");
console.log(machine.makeCoffee(1));
});