객체지향 프로그래밍 (Object-Oriented Programming, OOP)

객체지향 프로그래밍이란?

객체지향 프로그래밍(OOP)은 프로그램을 객체(object)라는 기본 단위로 나누어 설계하고 개발하는 프로그래밍 패러다임입니다. 객체는 데이터(속성)와 해당 데이터에 작용하는 함수(메서드)를 포함하는 독립된 단위입니다.

OOP는 현실 세계의 사물과 개념을 추상화하여 소프트웨어에 적용하며, 코드의 재사용성과 유지보수성을 높이는 데 중점을 둡니다.


주요 특징

  1. 캡슐화 (Encapsulation)
    • 책임을 나누는 작업.
    • 데이터와 메서드를 하나의 객체로 묶고, 외부로부터 객체 내부를 숨김(은닉성).
    • 객체 간 상호작용을 위해 공개된 인터페이스만 제공.
  2. 상속 (Inheritance)
    • 기존 클래스(부모 클래스)를 기반으로 새로운 클래스(자식 클래스)를 생성.
    • 코드 재사용성을 높이고, 계층 구조를 형성.
  3. 다형성 (Polymorphism)
    • 동일한 이름의 메서드가 클래스에 따라 다르게 동작.
    • 메서드 오버로딩(Overloading)과 오버라이딩(Overriding)을 통해 구현.
    • javascript는 type이 없기떄문에 typescript를 통해 interface 이용
  4. 추상화 (Abstraction)
    • 객체의 세부 구현을 숨기고, 필요한 기능만 노출.
    • 인터페이스와 추상 클래스를 통해 구현.

장점

  1. 코드 재사용성 증가
    • 상속을 통해 기존 코드를 재사용 가능.
    • 중복 코드를 줄이고 생산성을 향상.
  2. 유지보수 용이
    • 모듈화된 설계로 특정 객체만 수정해도 전체 시스템에 영향을 최소화.
  3. 가독성 향상
    • 현실 세계의 모델링과 유사해 코드의 이해가 용이.
  4. 확장성
    • 새로운 기능 추가가 쉬움.

단점

  1. 복잡성
    • 구조와 설계가 복잡해질 수 있음.
  2. 초기 개발 비용 증가
    • 설계와 객체 정의에 많은 시간이 소요.
  3. 성능 저하
    • 객체 간 메시지 전달 및 메서드 호출로 인해 절차적 프로그래밍에 비해 실행 속도가 느릴 수 있음.

예시 (절차적 프로그래밍에서 객체지향으로 리팩토링 해보기)

절차적인 프로그래밍

1. 고급언어를 이용한 절차작성
2. 제어구조를 이용한 흐름제어

< 단점 >
1. 코드가 길어진다. (통짜코드) / 확장성이 없다 / 유지보수가 어렵다 => 함수로 분리
2. 코드의 재사용이 어렵다. / 집중화 필요 => 함수로 분리
3. 예외처리가 안되어있다. (숫자가 아닌 값, 소수점, 음수값 등)
4. 변수의 스코프가 너무 넓은 문제 / 변수의 충돌이 발생 위험 => 지역화 필요
5. 데이터를 표현하고 있지 않다. / 데이터 구조화 필요 => 객체화
// 외부라이브러리 : npm install readline-sync
const readlineSync = require("readline-sync");
let kors = [];
let engs = [];
let maths = [];

let isRun = true;

// 헤더출력
console.log(
  `====================================
           성적관리 프로그램         
====================================`
);

while (isRun) {
  // 메뉴입력
  console.clear();
  console.log("------------------------------------");
  console.log("                메뉴선택             ");
  console.log("------------------------------------");

  console.log("1. 성적입력");
  console.log("2. 성적출력");
  console.log("3. 종료");
  console.log(">");
  let menu = readlineSync.question("");
  menu = parseInt(menu);

  switch (menu) {
    case 1: // 성적입력
      console.clear();
      console.log("------------------------------------");
      console.log("                성적입력             ");
      console.log("------------------------------------");

      let kor = readlineSync.question("kor: ");
      kor = parseInt(kor);
      let eng = readlineSync.question("eng: ");
      eng = parseInt(eng);
      let math = readlineSync.question("math: ");
      math = parseInt(math);

      kors.push(kor);
      engs.push(eng);
      maths.push(math);
      break;
    case 2: // 성적출력
      console.clear();

      console.log("------------------------------------");
      console.log("                성적출력             ");
      console.log("------------------------------------");
      console.log(`총인원: ${kors.length}명`);
      console.log("");

      // 평균을 기준으로 역순정렬
      // ??

      for (let i = 0; i < kors.length; i++) {
        let kor = kors[i];
        let eng = engs[i];
        let math = maths[i];

        // 총점 계산
        let total = kor + eng + math;
        // 평균 계산
        let avg = total / 3;

        console.log(`num: ${i + 1}`);
        console.log(`kor: ${kor}`);
        console.log(`eng: ${eng}`);
        console.log(`math: ${math}`);
        console.log(`total: ${total}`);
        console.log(`avg: ${avg}`);
        console.log("--------------------------");
      }

      console.log("계속하려면 엔터키를 누르세요.");
      readlineSync.question("");

      break;
    case 3: // 프로그램 종료
      isRun = false;
      break;
  }
}

// 푸터 출력
console.log("====================================");
console.log("Bye~~~");

2. 함수로 분리해서 관리

// app.js
// 구조적인 프로그래밍
// 1. 변수와 함수를 사용하여 프로그램을 구조화
// 2. 함수는 하나의 기능을 수행하도록 작성
// 3. 함수는 독립적이어야 함
// 4. 함수는 재사용이 가능해야 함
// 5. 함수는 일관된 이름으로 작성

import { inputMenu, inputScore, printHeader, printScore, printFooter } from "./lib.js";

let isRun = true;

printHeader();

while (isRun) {
    let menu = inputMenu();

    switch (menu) {
        case 1: 
            inputScore();
            break;
        case 2: 
            printScore();
            break;
        case 3: // 프로그램 종료
            isRun = false;
            break;
    }
}

printFooter();

// lib.js
import readlineSync from "readline-sync";
import Exam from "./exam.js";
import ExamService from "./exam-service.js";

const service = new ExamService();

export const printHeader = () => {
    console.log(
        `====================================
                성적관리 프로그램         
        ====================================`
    );
};

export const inputMenu = () => {
    // 메뉴입력
    console.clear();
    console.log("------------------------------------");
    console.log("                메뉴선택              ");
    console.log("------------------------------------");

    console.log("1. 성적입력");
    console.log("2. 성적출력");
    console.log("3. 종료");
    console.log(">");

    let menu = readlineSync.question("메뉴를 선택하세요: ");
    menu = parseInt(menu);
    return menu;
}

export const inputScore = () => {
    console.clear();
    console.log("------------------------------------");
    console.log("                성적입력              ");
    console.log("------------------------------------");

    let kor = readlineSync.question("kor: ");
    kor = parseInt(kor);
    let eng = readlineSync.question("eng: ");
    eng = parseInt(eng);
    let math = readlineSync.question("math: ");
    math = parseInt(math);

    service.add(new Exam(kor, eng, math));
}

export const printScore = () => {
    console.clear();

    console.log("------------------------------------");
    console.log("                성적출력              ");
    console.log("------------------------------------");
    console.log(`총인원: ${service.size()}명`);
    console.log("");

    // 평균을 기준으로 역순정렬
    const exams = service.getList(1, 3);

    exams.map((exam, index) => {
        const { kor, eng, math } = exam;

        console.log(`num: ${index + 1}`);
        console.log(`kor: ${kor}`);
        console.log(`eng: ${eng}`);
        console.log(`math: ${math}`);
        console.log(`total: ${exam.total()}`);
        console.log(`avg: ${exam.avg()}`);
        console.log("--------------------------");
    })

    console.log("계속하려면 엔터키를 누르세요.");
    readlineSync.question("");
}

export const printFooter = () => {
    console.log("====================================");
    console.log("Bye~~~");
};

// exam-service.js
export default class ExamService {
    #exams = [];

    constructor(exams = []) {
        this.#exams = exams;
    }

    get exams() {
        return this.#exams;
    }

    add(exam) {
        this.#exams.push(exam);
    }
    size() {
        return this.#exams.length;
    }
    getList(page = 1, size = 3) {
        return this.#exams.sort((a, b) => b.total() - a.total()).slice((page - 1) * size, (page * size) - 1);
    }
};

// exam.js
// 1. 함수를 이용한 모듈화 - function
function FuncExam(kor = 0, eng = 0, math = 0) {
    this.kor = kor;
    this.eng = eng;
    this.math = math;
}

FuncExam.prototype = {
    // 추가해주지 않으면 prototype의 체인이 깨짐
    // const exam1  = new Exam(100, 90, 80);
    // exam1.constructor === Exam => false
    constructor : FuncExam,
    total() {
        return this.kor + this.eng + this.math;
    },
    avg() {
        return this.total() / 3;
    }
};

// 2. 객체를 이용한 모듈화 - class
// 2015년 ES6에서 class 키워드가 추가
// 사실 class는 자바스크립트에서 기존의 프로토타입 기반 **상속**을 쉽게 사용하기 위한 문법적 설탕...
export  class Exam {
    // 데이터 은닉화(private) -> #을 붙이면 외부에서 접근 불가(보호모드가 들어간 변수)
    #kor = 0;
    #eng = 0
    #math = 0;

    // 공유 속성을 원할 경우 앞에 static을 붙혀 사용 (타입이 같으면 같은 값을 갖는 변수)
    static #description = "Exam 객체는 국어, 영어, 수학 성적을 관리하는 객체입니다"

    // 생성자는 이름을 갖지 않는 함수
    constructor(kor = 0, eng = 0, math = 0) {
        this.#kor = kor;
        this.#eng = eng;
        this.#math = math;
    }

    // getter 메서드
    get kor() { return this.#kor; }
    get eng() { return this.#eng; }
    get math() { return this.#math; }
    get description() { return Exam.#description; }
    
    //setter 메서드
    set kor(kor) { this.#kor = kor; }
    set eng(eng) { this.#eng = eng; }
    set math(math) { this.#math = math; }

    // 인스턴스가 아닌 클래스 메서드로 사용하기 위해 static 키워드 사용
    static create() {
        return new Exam();
    }

    total() {
        return this.#kor + this.#eng + this.#math;
    }
    avg() {
        return this.total() / 3;
    }
};

정리

  • 객체지향 프로그래밍은 현실 세계의 개념을 소프트웨어로 모델링하여 코드의 재사용성과 유지보수성을 향상시키는 강력한 패러다임입니다. 캡슐화, 상속, 다형성, 추상화라는 네 가지 주요 특징을 통해 복잡한 시스템을 보다 체계적으로 설계하고 관리할 수 있습니다.