타입스크립트와 객체지향 프로그래밍 (OOP)
프론트엔드에서의 객체지향 프로그래밍 개념 정리
최근 프로젝트를 진행하며, 또 현업에 치이는 이유로 포스팅에 여유가 없었습니다.
그러던 와중, typescript
로 다시 사이드 프로젝트를 진행하며 객체지향 프로그래밍(OOP
)에 대해서 개념 정리를 하려고 합니다.
TypeScript ?
typescript
는 javascript
의 슈퍼셋으로, javascript
에 정적 타입을 추가하여 개발의 안정성과 효율성을 높이고,
특히 협업 단계에서 작업의 효율성을 극대화 시킬 수 있는 언어입니다.
동적 언어인 javascript
와 달리 type
을 부여하여, 타입을 명시하고 컴파일 단계에서 타입 에러를 찾아내서 쉽게 디버깅할 수 있고,
이를 통해 개발 단계에서 안정적인 개발을 할 수 있다는 이점이 있습니다.
사실 typescript
가 무엇인지를 설명하는 것 보다는 typescript
와 OOP
객체 지향 프로그래밍에 관한 내용 정리를 할 예정이므로,
typescript
에 대해서는 간략하게 이 정도만 설명하도록 하겠습니다.
객체 지향 프로그래밍 (OOP)
객체 지향 프로그래밍을 알기 위해선 절차 지향 프로그래밍에 대한 이해도 어느정도 필요합니다.
- 절차 지향 프로그래밍
절차 지향 프로그래밍이란, 프로그램 전체가 유기체처럼 유기적으로 연결이 되도록 만들어주는 기법입니다.
즉, 프로그램을 일련의 순차적인 절차로 구성하는 방식인데,
이 방식은 프로그램의 흐름을 중시하고, 큰 문제를 작은 문제로 나누어 순차적으로 해결해 나가는 방법입니다.
예를 들어, 일상 생활에서 요리를 하기 위해 레시피를 준비하는 것과 유사하다고 할 수 있습니다.
레시피에 맞춰 재료들을 준비하고, 각 단계별로 순서에 따라 요리를 만들어 완성을 합니다.
각 단계에서는 특정 작업을 수행하고, 전체 요리 과정은 이러한 특정 작업들의 절차가 이루어진 집합체가 됩니다.
- 객체 지향 프로그래밍
객체 지향 프로그래밍이란, 프로그램을 객체의 집합으로 modeling
해서, 각 객체가 서로 상호작용하는 방식으로 구성이 되는 기법입니다.
객체는 데이터 (속성)과 그 데이터를 조작할 수 있는 함수를 포함하게 됩니다.
이는 코드의 재사용성이나, 확장성, 유지보수성을 높이는 것에 유리합니다.
우리가 일상생활에서 사용하는 스마트폰을 예시로 들 수 있습니다.
스마트폰은 다양한 어플리케이션들이 있고, 각각의 어플리케이션은 독립적인 기능과 데이터를 가지고 있는데,
우리는 이러한 어플리케이션을 상호작용하며 내가 원하는 작업을 수행하게 됩니다.
대다수의 front-end
개발자들은 class
기반의 객체 지향 프로그래밍을 하여 개발을 하고있지 않습니다.
그 이유는 과거 javascript
에는 class
가 존재하지 않았고, 그로 인해 객체 지향 프로그래밍을 할 수가 없었습니다.
그런데 특이하게도,
javascript
는prototype
기반의 객체지향 프로그래밍 언어입니다.
현재는 시간이 지나서 ES6
부터는 class
문법이 추가되었지만, 이것도 단순히 prototype
기반 함수라는 것을 보여줄 뿐, javascript
자체가 객체 지향 프로그래밍 언어가 되었다는 것은 아닙니다.
객체 지향 프로그래밍의 특징
-
캡슐화 : 데이터와 데이터를 처리하는 함수를 하나의 단위, 즉 객체로 묶어서 관리합니다.
이를 바탕으로 외부에서 객체의 내부 구조를 굳이 알 필요 없이도 객체가 제공하는 인터페이스를 통해 상호작용할 수 있습니다. -
상속성 : 한
class
의 특성을 다른class
에서도 이어받아 사용할 수 있습니다.
부모, 자식과의 관계로 생각을 하면 되며, 바로 이것을 통해 코드의 재사용성을 높이고, 중복을 줄일 수 있습니다. -
다형성 : 동일한 이름의 메서드가 다른 클래스에서 다른 작업을 수행할 수 있도록 해줍니다.
인터페이스의 구현이나override
를 통해 구현되며 이는 코드의 유연성을 높여줍니다.
TypeScript 에서의 객체 지향 프로그래밍
간단한 예시를 통해 설명을 하도록 하겠습니다.
우리가 은행에서 사용하는 계좌를 나타내는 예시를 작성하려고 합니다.
class Account {
private ownerName: string;
protected balance: number;
}
Account
클래스에서 ownerName
과 balance
로 필드를 선언해보았다.
private
로 선언된 필드는 비공개 필드로, 클래스 외부에서는 접근할 수 없습니다.
protected
로 선언된 balance
필드는 계좌의 잔액을 저장하는 필드로, 이 클래스와 상속받은 자식 클래스에서 접근이 가능합니다.
class Account {
/* 위의 내용 */
constructor(ownerName: string, balance: number = 0) {
this.ownerName = ownerName;
this.balance = balance;
}
}
constructor
생성자 함수로 Account
클래스의 instance
를 생성할 때 호출됩니다.
ownerName
과 balance
를 매개 변수로 받으며, balance
의 기본값은 0입니다.
instance
란 클래스로부터 생성된 객체를 의미합니다.
그리고,this
는 현재 객체를 가리키는 데 사용됩니다.
this
는 현재 실행한 것의 객체를 참조하고,class
내부에서this
를 사용하면, 해당 메서드가 속한instance
의 속성이나, 다른 메서드에 접근할 수 있게 해줍니다.
class Account {
private ownerName: string;
protected balance: number;
constructor(ownerName: string, balance: number = 0) {
this.ownerName = ownerName;
this.balance = balance;
}
deposit(amount: number): void {
this.balance += amount;
console.log(`입금액: ${amount}, 현재 잔고: ${this.balance}`);
}
withdraw(amount: number): void {
if (amount <= this.balance) {
this.balance -= amount;
console.log(`출금액: ${amount}, 현재 잔고: ${this.balance}`);
} else {
console.log("잔액이 부족합니다.");
}
}
getBalance(): number {
return this.balance;
}
}
각각의 method
들을 작성해보았습니다.
deposit
은 입금을 처리하는 메서드이고, 입금할 금액 amount
를 매개변수로 받아서, 현재 잔액에 더하고, 새로운 잔액을 출력합니다.
withdraw
는 deposit
과 반대로 출금을 처리하는 메서드이고, 출금 금액이 amount
보다 작거나 같으면, 잔액에서 해당 금액을 차감하고 새로운 잔액을 출력합니다.
잔액이 부족할 경우에는 잔액이 부족합니다.
를 출력하게 됩니다.
getBalance
는 현재 잔액을 가져오는 메서드입니다.
class SavingsAccount extends Account {
private interestRate: number;
constructor(ownerName: string, balance: number, interestRate: number) {
super(ownerName, balance);
this.interestRate = interestRate;
}
addInterest(): void {
const interest = (this.balance * this.interestRate) / 100;
this.balance += interest;
console.log(`이자: ${interest}, 현재 잔고: ${this.balance}`);
}
}
SavingsAccount
는 Account
를 상속받은 클래스입니다.
마찬가지로 필드 선언 단계에서 interestRate
는 private
로 선언 되었으며, 이는 SavingsAccount
의 instance
내부에서만 접근이 가능합니다.
constructor
생성자 함수에서 SavingsAccount
클래스의 instance
를 생성할 때 호출됩니다.
여기서 부모 클래스인 Account
의 생성자를 super
를 호출하여 초기화 시키고, 이자율을 설정합니다.
addInterest
는 이자를 계산해서 잔액에 추가를 하는 method
입니다.
여기서 이자는 현재 잔액인 balance
와 위에서 선언한 이자율 interestRate
를 사용해서 계산이 되고,
잔액에 이자를 더해, 현재 잔액을 출력합니다.
const myAccount = new SavingsAccount("흥부", 1000, 2);
myAccount.deposit(500);
myAccount.addInterest();
myAccount.withdraw(200);
이런 방식으로 instance
를 생성하고 method
를 호출할 수 있습니다.
SavingsAccount
클래스의 instance
를 생성하고, 이름과 잔액, 이자율을 설정해주었습니다.
이제 myAccount
에서 deposit
메서드를 호출해서 500
원을 입금하는데, 이를 통해 새로운 잔액을 출력하게 됩니다.
addInterest
를 통해 이자를 계산하고 현재 잔액에 추가하여, 새로운 잔액을 출력시킵니다.
그 뒤 withDraw
를 통해 200
원을 출금합니다.
단순하게 작성했지만, 이 코드에서는 위에서 언급한 캡슐화, 상속성, 다형성 의 개념을 모두 적용시킨 것입니다.
Account
는 일반적인 계좌에 대해서 정의를 했고, SavingsAccount
에서 Account
의 특정 계좌에 특정 행동을 추가하여 부모인 Account
의 기능을 확장했습니다.
기본적인 개념과 작동 방식만을 보여주는 포스팅이지만, 내가 언급하지 않은 다양한 기능들이 있습니다.
시간이 날 때, 또 한 번 예제를 만들어보면서 개념을 다져봐야겠습니다.