TIL 29 | 추상화, 캡슐화, 상속, 다형성

CHAEIN·2021년 7월 19일
0

JavaScript

목록 보기
25/31
post-thumbnail

추상화

추상은 여러가지 사물이나 개념에서 공통되는 특성이나 속성 따위를 추출하여 파악하는 작용을 의미한다. 이에 따라 추상화란 구체적인 정보에서 목적에 맞는 꼭 필요한 핵심만 추출하는 것을 추상화라고 한다. 개발에서는 단순히 클래스를 설계하는 것 뿐만 아니라 코딩하는 모든 과정이 모두 이 추상화에 해당한다.

이 추상화 과정에서 주의해야할 부분은 클래스 및 변수, 메소드의 이름을 잘 짓는 것이다. 그래야 다른 사람이 해당 코드를 보고도 쉽게 이해할 수 있기 때문이다. 하지만 이름을 잘 짓는 것만으로도 모두가 이해하는 추상화를 달성하기 어렵다. 따라서 코드에 주석을 단다던가 각각의 객체와 프로퍼티를 설명하는 별도의 문서를 작성하는 등 부가적인 액션을 통해 추상화를 보완할 수 있다.

캡슐화

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

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

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


const user1 = new User('pva0046@gmail.com', '1992-03-01')
user1.email = 'coding' // 실수로 이메일이 아닌 값으로 정보가 교체된 경우, 애초에 설계 시점부터 이러한 실수를 방지할 수 있도록 설계해야한다. 

이러한 상황을 방지하기 위해선 getter, setter 메소드를 활용할 수 있다.

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

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

  // 프로퍼티의 값을 읽는 게터 메소드
  get email(){
    return `Email Address is ${this._email}`; // _email이 아닌 그냥 이메일을 가리키도록 함
  }

  // email 프로퍼티에 직접 접근하는 것을 막는 setter 메소드
  // 앞으로 email이란 값을 user1.email = 'chris rover' 직접 설정하려 할 떄마다 set 함수가 실행된다.
  set email(address){
    if(address.includes('@')){ // 값에 @이 포함되어있는지 여부 검정
      this._email = address; // 숨기고자 하는 email 프로퍼티의 네임에 _를 붙여 새 프로퍼티를 생성하고 정보를 할당한다. 
    } else {
      throw new Error ('invalid email address');
    }
  }
}


const user1 = new User('pva0046@gmail.com', '1992-03-01')
user1.email = 'c@' // 실수로 이메일이 아닌 값으로 정보가 교체된 경우, 애초에 설계 시점부터 이러한 실수를 방지할 수 있도록 설계해야한다. 
console.log(user1.email) //getter 메소드 이름을 email로 설정했기 때문에 user1.email은 email메소드를 호출하고 getter 메소드 내부에 정의된 값을 리턴하게 된다. 

하지만 위의 방식도 완전하지 않다. user1.email로 접근 하는 방법은 제한됐지만 user1._email로 접근할 수 있는 방법은 여전히 남아있기 때문이다. Java는 private이라는 키워드로 완전한 캡슐화를 할 수 있지만 비슷한 기능의 키워드가 없는 자바스크립트는 클로저 개념을 방식을 이용해 완전한 캡슐화를 이룰 수 있다.

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);

user1은 createUser함수의 리턴값인 user를 할당받았다. createUser함수를 보면 user1에 할당된 user 객체 바깥으로 _email이라는 변수가 선언되어있다. 그러나 user1.email을 사용하면 getter 메소드를 통해 user 밖에 선언된 _email 데이터의 값을 읽을 수 있다. 이는 자바스크립트의 클로저 덕분에 가능한 현상이다. 하지만 user1.email = '어쩌고' 처럼 새로운 값을 할당하고자 하면 setter 메소드에 의해 Uncaught Error invalid address라는 오류가 발생하여 해당 데이터의 수정이 불가능하다. 또한 user1._email로 직접 접근하려 해도 user1객체 자체에는 _email 프로퍼티가 없고 getter와 setter메소드를 이용하지 않아 바깥의 _email에도 접근이 불가능하기 때문에 undefined가 출력된다. 이처럼 클로저 개념을 사용하면 보호되어야할 변수 뿐만 아니라 객체에 프로퍼티로 존재할 수 있는 요소 모두 자바스크립트로 완전한 캡슐화를 할 수 있다.

최근 자바스크립트도 자바와 같이 public과 private 키워드를 활용해 간편하게 캡슐화 할 수 있는 기능이 추가되었다고 한다. 그러나 지원하는 브라우저가 적어 상용화에는 시간이 걸릴 것으로 보인다.

추상화와 캡슐화의 차이

캡슐화가 코드나 데이터의 은닉에 포커스가 맞춰져있다면, 추상화는 클래스를 사용하는 사람이 필요하지 않은 메소드 등을 노출시키지 않고, 단순한 이름으로 정의하는 것에 포커스가 맞춰져 있다. 객체 지향 프로그래밍 언어의 대표인 JAVA 나 자바스크립트의 유연함을 보완한 typescript의 경우 메소드의 속성만 정의한 interface라는 기능이 존재하는데 사실상 이것이 추상화의 본질이다.

상속

상속은 부모 클래스의 특징을 자식 클래스가 물려받는 것이다. 이로 인해 코드의 재사용성을 높일 수 있다.

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); // User 클래스에서 상속받을 프로퍼티를 정의한다. 
    this.level = level; 
  }
  
  // User에서 상속받을 메소드는 따로 정의해주지 않아도 사용 가능하다.
  streamMusicForFree(){
    console.log(`Free music ${this.email})
  }
}

다형성

다형성은 특정 기능을 선언(설계)부분과 구현(동작)부분으로 분리한 후 구현부분을 다양한 방법으로 만들어 선택하여 사용할 수 있게 하는 기능이다. 자바의 경우 interface, 추상클래스 등과 같이 선언 부분과 구현 부분을 완전하게 구분할 수 있는 문법이 존재하지만 자바스크립트에는 사실상 그런 문법이 존재하지 않기 때문에 부모 클래스에서 선언된 메소드를 자식 클래스에서 오버라이딩 하는 방식으로 활용된다.

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

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


class PremiumUser extends User { //User class 상속
  constructor(email, birthdate, level){
    super(email, birthdate); 
    this.level = level; 
  }
  
  buy(item){ // buy 메소드 오버라이딩
    console.log(`${this.email} buys ${item.name}` with a 5% discount)
  }
}

0개의 댓글