Super Kawaii Cute Cat Kaoani
본문 바로가기
🏃‍♀️ 대외활동/Codeit Boost-Node.js

5주차 스터디 자바스크립트 객체 지향 기본기 (2) [코드잇 부스트 백엔드 스터디 ]

by wonee1 2024. 6. 4.
728x90

 

 

 

<자바스크립트 객체 지향 기본기>

 

2. 객체 지향 프로그래밍의 4개의 기둥

 

📌 객체 지향 프로그래밍의 핵심 개념 4가지

추상화
캡슐화
상속
다형성



추상화

 

어떤 구체적인 존재를 원하는 방향으로 간략화해서 나타내는 것

→ 따라서 클래스를 설계하는 것도 모두 추상화 과정에 해당!

 

💡클래스의 이름, 프로퍼티와 메소드의 이름을 잘 짓는 것 중요

 

 

추상화 직접 해보기

 

실습

각 프로퍼티와 메소드 이름 위에 그 의미를 나타내는 간단한 주석을 직접 작성

 

 

 

class BankAccount {
  constructor(name, money) {
    // 소유주
    this.holder = name;
    // 잔액
    this.balance = money;
  }

  // 입금하기
  deposit(money) {
    this.balance += money;
  }

  // 출금하기
  withdraw(money) {
    if (this.balance - money < 0) {
      console.log('Insufficient balance');
    } else {
      this.balance -= money;
    }
  }

  // 이체하기
  transfer(money, anotherAccount) {
    const account = anotherAccount;
    if (this.balance - money < 0) {
      console.log('Insufficient balance');
    } else {
      this.balance -= money;
      account.balance += money;
    }
  }
}

 

 

 

캡슐화

 

객체의 특정 프로퍼티에 직접 접근하지 못하도록 막는 것

💡특정 프로퍼티에 대한 접근을 미리 정해진 메소드들을 통해서만 가능하도록 하는 것

 

 

setter 메소드 : 특정값을 설정할 때마다 파라미터로 받아와 설정할 수 있고 추가적으로 코드를 실행

getter 메소드 : 프로퍼티의 값을 구하는 함수

           * getter 메소드 프로퍼티의 값을 읽는 용도로 사용 -> 파라미터를 따로 써줄 필요 없음

 

 

class User{

   constructor(email, birthdate){ 
    this.email = email;
    this.birthdate= birthdate; 
   }

   buy(item){
    console.log(`${this.email} buys ${item.name}`); 
  }

get email(){
  return this._email;
}//getter 메소드 프로퍼티의 값을 읽는 용도로 사용 -> 파라미터를 따로 써줄 필요 없음 


set email(address){//setter 메소드 

    if(address.includes(`@`)){
      this._email=address; // 만약 address 안에 at(@)이 포함된다면
      //이 email에 address를 한다

    }else{
      throw new Error('invaild email address');
    }
  }//값에 대한 유효성 검사 

}

const item ={
  name: '스웨터',
  price: 3000,

}

const user1 = new User('chris123@google.com','1992-03-21');


console.log(user1.email);
console.log(user1.birthdate);
user1.email= 'chirs robert';

 

숨기고자 하는 프로퍼티의 이름은 맨 앞에 언더바 _ 가 붙은 형식으로 써 줄 때가 많다 (하나의 관습) 

 

 

 

 

 

1. 완벽한 캡슐화를 하는 법

 

JavaScript에서도 다른 방식으로 우회해서 완벽한 캡슐화를 할 수는 있음.

클로저(Closure)라고 하는 개념을 응용해서 적용

 

 

function createUser(email, birthdate) {
  let _email = email;

  const user = {
    birthdate,

    get email() {
      return _email;
    },

    set email(address) {
      if (address.includes('@')) {
        _email = address;
      } else {
        throw new Error('invalid email address');
      }
    },
  };

  return user;
}
const user1 = createUser('chris123@google.com', '19920321');
console.log(user1.email);

-----------------------------------------------------

function createUser(email, birthdate) {
  let _email = email;

  const user = {
    birthdate,

    get email() {
      return _email;
    },

    set email(address) {
      if (address.includes('@')) {
        _email = address;
      } else {
        throw new Error('invalid email address');
      }
    },
  };

  return user;
}

const user1 = createUser('chris123@google.com', '19920321');
console.log(user1._email); // _ 추가

//undefiend 출력됨 user1 객체의 _email 프로퍼티에 접근하려고 하면, 
//user1 객체 자체 내에는 _email이라고 하는 프로퍼티가 없고, 
//바깥의 _email 변수에 현재 접근할 수도 없기 때문

 

코드해석 

createUser라고 하는 Factory function이 보인다. 이때 생성하려는 user 객체 안에 _email 프로퍼티가 있는 게 아니라,

 

(1) createUser 함수 안에,

(2) 그리고 user 객체 바깥에 _email이라는 변수가 있다

 

대신에 user 객체 안에는 _email 변수의 값을 읽고 쓸 수 있는 email이라는 getter/setter 메소드가 있다.

 

 

클로저란 자바스크립트에서 어떤 함수와 그 함수가 참조할 수 있는 값들로 이루어진 환경을 하나로 묶은 것을 의미

 

→ 함수가 정의된 당시에 참조할 수 있었던 변수들을 계속 참조할 수 있는 상태의 함수를 클로저라고 한다

자바스크립트에서는 클로저라는 개념으로 해당 환경을 함수와 함께 그대로 유지시켜주는 것이다

 

 

 

 

2. 메소드도 캡슐화할 수 있다

 

function createUser(email, birthdate) {
  const _email = email;
  let _point = 0;

  function increasePoint() {
    _point += 1;
  }

  const user = {
    birthdate,

    get email() {
      return _email;
    },

    get point() {
      return _point;
    },

    buy(item) {
      console.log(`${this.email} buys ${item.name}`);
      increasePoint();
    },
  };

  return user;
}

const item = {
  name: '스웨터',
  price: 30000,
};

const user1 = createUser('chris123@google.com', '19920321');
user1.buy(item);
user1.buy(item);
user1.buy(item);
console.log(user1.point);
user1.increasePoint(); // user1 객체로 increasePoint 직접 호출 -> 에러 발생

 

코드해석 

 user1 객체에는 increasePoint라는 메소드가 없기 때문에 직접 호출하면 에러가 발생한다.  increasePoint가 유저 객체 안에서 적절한 곳에 사용되어야 하고, 아무렇게나 함부로 호출해서는 안 되는 메소드라고 가정하고 이렇게 캡슐화한다

 

 

 


 

상속 

 

하나의 객체가 다른 객체의 프로퍼티와 메소드를 물려받을 때

 

class User{

   constructor(email, birthdate){ //super 
    this.email = email;
    this.birthdate= birthdate; 
   }

   buy(item){
    console.log(`${this.email} buys ${item.name}`); 
  }

}//부모 클래스 


class PremiumUser extends User {//자식 클래스 
  constructor(email, birthdate, level){
    this.level=level;
  }

  streamMusicForFree(){
    console.log(`Free music streaming for ${this.email}`);

  }//새롭게 생겨난 부분 
  
}//상속을 할 땐 중복코드는 제거 

const item ={
  name: '스웨터',
  price: 30000,

}

const pUser1= new PremiumUser('chris123@google.com', '19920321');
console.log(pUser1.email);
console.log(pUser1.birthdate);
console.log(pUser1.level);
pUser1.buy(item);
pUser1.streamMusicForFree();


//코드의 재사용성이 좋아진다

 

→  이대로 코드를 실행하면 에러가 발생한다

 

에러 발생

 

💡자식 클래스 안에서 super 생성자를 호출해야함  

 

 

 

super

 

부모 클래스에 있는 생성자 함수를 의미한다 (부모의 constructor 부분)

💡자식 클래스로 객체를 만드려고 할 때는 반드시 부모 생성자 객체를 먼저 호출 해줘야한다

 

 

class User{

   constructor(email, birthdate){ 
    this.email = email;
    this.birthdate= birthdate; 
   }

   buy(item){
    console.log(`${this.email} buys ${item.name}`); 
  }

}//부모 클래스 


class PremiumUser extends User {//자식 클래스 
  constructor(email, birthdate, level){
    super(email,birthdate);
    this.level=level;
  }

  streamMusicForFree(){
    console.log(`Free music streaming for ${this.email}`);

  }//새롭게 생겨난 부분 
  
}//상속을 할 땐 중복코드는 제거 

const item ={
  name: '스웨터',
  price: 30000,

}

const pUser1= new PremiumUser('chris123@google.com', '19920321',3);
console.log(pUser1.email);
console.log(pUser1.birthdate);
console.log(pUser1.level);
pUser1.buy(item);
pUser1.streamMusicForFree();


//코드의 재사용성이 좋아진다

 

 

☑️실행결과

 

 

 

 


 

 

 

다형성

 

많은 형태를 갖고 있는 성질

 

 

☑️오버라이딩

자식 클래스에서 부모 클래스와 동일한 이름의 메소드를 정의하고 그 내용을 다르게 채우는 것

 

 

class User{

   constructor(email, birthdate){ 
    this.email = email;
    this.birthdate= birthdate; 
   }

   buy(item){
    console.log(`${this.email} buys ${item.name}`); 
  }

}//부모 클래스 


class PremiumUser extends User {//자식 클래스 
  constructor(email, birthdate, level){
    super(email,birthdate);
    this.level=level;
  }

  streamMusicForFree(){
    console.log(`Free music streaming for ${this.email}`);

  }

  buy(item){
    console.log(`${this.email} buys ${item.name} with a 5% discount`); 
  }
}

const item ={
  name: '스웨터',
  price: 30000,

}
const user1= new User('chris123@google.com', '19920321');
const user2= new User('rachel3@google.com', '19820221');
const user3= new User('brian@google.com', '19900121');


const pUser1= new PremiumUser('tommy@google.com', '19901207',3);
const pUser2= new PremiumUser('helloMike@google.com', '19990915',1);
const pUser3= new PremiumUser('alicek@google.com', '20010722',2);

const users=[user1, pUser1, user2, pUser2, user3, pUser3];

users.forEach((user)=>{
  user.buy(item);
});

user1.buy(item);
pUser1.buy(item);

 

 

☑️실행결과

 

 

 

 

instacneof 연산자

 

💡어느 클래스로 만든 객체인지 확인하고 싶을 때 instacneof 연산자 사용

 

 

class User{

   constructor(email, birthdate){ 
    this.email = email;
    this.birthdate= birthdate; 
   }

   buy(item){
    console.log(`${this.email} buys ${item.name}`); 

  }

}//부모 클래스 


class PremiumUser extends User {//자식 클래스 
  constructor(email, birthdate, level){
    super(email,birthdate);
    this.level=level;
  }

  streamMusicForFree(){
    console.log(`Free music streaming for ${this.email}`);

  }

  buy(item){
    super.buy(item);//부모클래스 buy메소드 호출
    this.point+=item.price*0.05;
  }
}

const item ={
  name: '스웨터',
  price: 30000,

}
const user1= new User('chris123@google.com', '19920321');
const user2= new User('rachel3@google.com', '19820221');
const user3= new User('brian@google.com', '19900121');


const pUser1= new PremiumUser('tommy@google.com', '19901207',3);
const pUser2= new PremiumUser('helloMike@google.com', '19990915',1);
const pUser3= new PremiumUser('alicek@google.com', '20010722',2);

const users=[user1, pUser1, user2, pUser2, user3, pUser3];

users.forEach((user)=>{
  console.log(user instanceof PremiumUser);

});

자식 클래스로 만든 객체는 부모 클래스로 만든 객체로도 인정된다

 

 

 

☑️실행결과

 

 

 

 

static 프로퍼티와 static 메소드

 

 

static 프로퍼티

static 메소드

 

 

→ 클래스에 직접적으로 딸려있는 프로퍼티와 메소드

💡즉 클래스로 객체를 만들었을 때 객체어서 활용할 프로퍼티와 메소드가 아니라 클래스 자체로 접근해서 사용할 때 !

 

 

 

class Math{
  static PI=3.14;

  static getCircleArea(radius){
    return Math.PI*radius*radius;
  }
}
//기존값 수정 가능 

Math.PI=3.141592;
Math.getCircleArea = function(width, height){
  return width * height;
}

console.log(Math.PI);
console.log(Math.getCircleArea(4,5));

console.log(Date.now());

 

 

 

클래스는 파일 하나당 하나씩

 

클래스와 메인 로직(main.js)을 파일별로 쪼개서 작성한다.

 

*모듈 내부의 것을 공개하고(export), 다른 모듈의 것을 가져오는(import)

 

실무에서는 이렇게 파일 하나당 클래스 하나를 두고 외부에 공개하는 방식을 많이 사용한다 그래야 코드를 좀더 편리하게 관리할 수 있기 때문

728x90