상세 컨텐츠

본문 제목

[Node.js] 심화 이론 (1-3) (객체 지향 구분 기준: 캡슐화 / 다형성 / 상속 / 추상화)

notes

by 서울의볼 2024. 2. 19. 10:09

본문

심화주차 강의 (1강~3강) 내용을 정리한 것임.

 

3줄 요약:

좋은 설계란 요구사항 수행과 유연한 변경을 위한 가독성 높은 코드이며, 객체지향은 기능을 객체로 취급하여 캡슐화, 상속, 다형성에 초점을 둔 프로그래밍 패러다임 중 하나의 방식임.

이를 통해 코드의 가독성과 유지보수성을 높이며, 캡슐화는 중요 정보를 숨기고, 상속은 코드 재사용성을 높이며, 추상화는 코드 간결성을 유지하고, 다형성은 객체의 다양한 동작을 표현함.

 

  • 좋은 설계란 요구하는 기능을 정확히 수행함과 동시에 추후의 변경을 매끄럽게 수용할 수 있는 설계. 이해하기 쉽고 가독성 있는 깔끔한 코드인 것!
  • 객체 지향은 소프트웨어 개발에서 주요 구성 요소를 기능이 아닌 객체로 삼으며 "어떤 객체가 어떤 일을 할 것인가"에 초점을 맞춘 것. 즉, 객체를 도출하고 각각의 역할을 명확하게 정의하는 것에 초점을 맞춤.
    • 절차 지향과 구분되는데, 이때까지 내가 라우터를 통해 하나씩 로직을 수행한 것이 절차지향임.
    • 구분 방법은 아래와 같음:
      • 캡슐화, 다형성, 상속을 지원 하는가? // 여기서 클래스는 객체를 생성할 때 사용하는 틀이며, new 키워드를 통해 객체를 생성할 수 있음.
      • 데이터 접근 제한(Access modifier)이 가능한가? // 데이터 접근 제한은 특정 데이터에 대한 외부 접근을 제한하여, 데이터 안정성과 보안을 높임.
  • 객체지향 프로그래밍(OOP)란 상태(데이터)와 그 데이터를 조작하는 프로세스(메서드)가 같은 모듈 내부에 배치되는 프로그래밍 방식을 의미함. 객체 지향 프로그래밍의 핵심 원칙에 대한 설명임:
    • 캡슐화 (Encapsulation): 객체 내부의 세부적인 사항을 감추는 것, 즉 중요한 정보를 외부로 노출시키지 않도록 만드는 것을 캡슐화(Encapsulation)라고 함.
      • 자바스크립트는 완벽한 캡슐화를 지원 안. 그러나, 개발자들은 변수 앞에 언더바(_)를 붙여 내부의 변수를 숨긴것 “처럼” 나타내는 규칙을 따르곤 함. 하기의 Typescript 예제 참고:
      •  
      • /** Encapsulation **/ class User { private name: string; // name 변수를 외부에서 접근을 할 수 없게 만듭니다. private age: number; // age 변수를 외부에서 접근을 할 수 없게 만듭니다. setName(name: string) { // Private 속성을 가진 name 변수의 값을 변경합니다. this.name = name; } getName() { // Private 속성을 가진 name 변수의 값을 조회합니다. return this.name; } setAge(age: number) { // Private 속성을 가진 age 변수의 값을 변경합니다. this.age = age; } getAge() { // Private 속성을 가진 age 변수의 값을 조회합니다. return this.age; } } const user = new User(); // user 인스턴스 생성 user.setName('이용우'); user.setAge(30); console.log(user.getName()); // 이용우 console.log(user.getAge()); // 30 console.log(user.name); // Error: User 클래스의 name 변수는 private로 설정되어 있어 바로 접근할 수 없습니다.
      • User클래스의 name, age 멤버 변수는 클래스 외부에서는 어떠한 방법으로도 직접 접근을 할 수 없음. 오로지 setter만 변수를 변경할 수 있고, getter만 변수를 조회할 수 있게 되어있음. 즉, User 클래의 중요한 정보를 외부로 노출시키지 않도록 만드는 캡슐화(Encapsulation)를 따르는 코드를 작성하게 됨.
    • 상속(Inheritance): 하나의 클래스가 가진 특징(함수, 변수 및 데이터)을 다른 클래스가 그대로 물려 받는 것을 말함. 이미 정의된 상위 클래스의 특징을 하위 클래스에서 물려받아 코드의 중복을 제거하고 코드 재사용성을 증대시킴.
      • 아래처럼 부모클래스를 상속받는 자식 클래스 여러개를 생성할 수도 있음:
      • /** Inheritance **/
        class Mother { // Mother 부모 클래스
          constructor(name, age, tech) { // 부모 클래스 생성자
            this.name = name;
            this.age = age;
            this.tech = tech;
          }
          getTech(){ return this.tech; } // 부모 클래스 getTech 메서드
        }
        
        class Child extends Mother{ // Mother 클래스를 상속받은 Child 자식 클래스
          constructor(name, age, tech) { // 자식 클래스 생성자
            super(name, age, tech); // 부모 클래스의 생성자를 호출
          }
        }
        
        const child = new Child("이용우", "28", "Node.js");
        console.log(child.name); // 이용우
        console.log(child.age); // 28
        console.log(child.getTech()); // 부모 클래스의 getTech 메서드 호출: Node.js
      • 부모클래스의 메서드, 데이터/변수, 함수를 상속시킬 수 있음! 그렇게 되면 코드 수정시 부모 클래스만 수정해도 자식 클래스들은 별도의 수정이 필요 없어 활용성이 좋아짐.
    • 추상화(Abstraction): 객체에서 공통된 부분을 모아 상위 개념으로 새롭게 정의하는 것을 추상화(Abstraction)라고 함. 즉, 불필요한 세부 사항을 생략하고, 중요한 특징만을 강조함으로써 코드를 더욱 간결하고 관리하기 쉽게 만드는 원칙임.
      • 클래스를 설계할 때, 공통적으로 묶일 수 있는 기능을 추상화(Abstraction) → 추상 클래스(Abstract Class) → 인터페이스(Interface) 순으로 정리한다면, 여러 클래스 간의 일관성을 유지하면서, 다양한 형태로 확장될 수 있는 코드, 즉 다형성(Polymorphism)이 가능해짐.
      • 여기서 인터페이스(interface)란, 클래스 정의할 때 메소드와 속성만 정의하여 인터페이스에 선언된 프로퍼티 또는 메소드의 구현을 강제하여 코드의 일관성을 유지함.
      • /** Abstraction **/
        interface Human {	--> Human이란 인터페이스는 name이란 변수와 set/get 두 가지 메서드를 가짐
          name: string;
          setName(name);
          getName();
        }
        
        // 인터페이스에서 상속받은 프로퍼티와 메소드는 구현하지 않을 경우 에러가 발생합니다.
        class Employee implements Human {
          constructor(public name: string) {  }
          
          // Human 인터페이스에서 상속받은 메소드
          setName(name) { this.name = name; }
          
          // Human 인터페이스에서 상속받은 메소드
          getName() { return this.name; }
        }
        
        const employee = new Employee("");	--> employee 인스턴스 생성
        employee.setName("이용우"); // Employee 클래스의 name을 변경하는 setter
        console.log(employee.getName()); // Employee 클래스의 name을 조회하는 getter
      • 동일한 인터페이스인 Human 인터페이스를 구현하는 모든 클래스는 해당 인터페이스에 선언된 프로퍼티와 메서드를 구현해야 함을 보장하게 됨. 이로 인해 코드의 일관성을 유지할 수 있게된 것.
    • 다형성(Polymorphism): 하나의 객체(클래스)가 다양한 형태로 동작하는것을 의미함. 이는 객체가 가진 특성에 따라 같은 기능이 다르게 재구성되는 것을 의미함. 즉, 동일한 메서드나 함수 명을 사용하더라도, 클래스마다 그 메서드가 다르게 동작하는 것이 다형성의 핵심인 것.
      • /** Polymorphism **/
        class Person {
          constructor(name) { this.name = name; }
        
          buy() {}
        }
        
        class Employee extends Person {
          buy() { console.log(`${this.constructor.name} 클래스의 ${this.name}님이 물건을 구매하였습니다.`); }
        }
        
        class User extends Person {
          buy() { console.log(`${this.constructor.name} 클래스의 ${this.name}님이 물건을 구매하였습니다.`); }
        }
        
        const employee1 = new Employee("이용우");
        const employee2 = new Employee("김창환");
        const user1 = new User("이태강");
        const user2 = new User("김민수");
        
        const personsArray = [employee1, employee2, user1, user2];
        // personsArray에 저장되어 있는 Employee, User 인스턴스들의 buy 메소드를 호출합니다.
        personsArray.forEach((person) => person.buy());
        
        // Employee 클래스의 이용우님이 물건을 구매하였습니다.
        // Employee 클래스의 김창환님이 물건을 구매하였습니다.
        // User 클래스의 이태강님이 물건을 구매하였습니다.
        // User 클래스의 김민수님이 물건을 구매하였습니다.
      • 위의 personsArray.forEach() 예제에서 person 변수는 Person 클래스를 상속받은 Employee 또는 User 클래스의 인스턴스를 참조함.
      • 여기서, 각 인스턴스의 buy 메서드를 호출하는 것은 동일하지만, Employee와 User 클래스의 buy 메서드는 서로 다른 행위를 수행하고 있는 것을 확인할 수 있습니다. 이것이 바로 다형성(Polymorphism)의 특징임.

 

관련글 더보기