본문 바로가기
JavaScript

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

by nacjji 2022. 12. 10.
객체지향 프로그래밍

프로그램의 처리단위가 객체인 프로그래밍 방법

현실 세계를 모델링하는 대표적인 프로그래밍 패러다임 

데이터와 프로세스가 동일한 모듈 내부에 위치하도록 프로그래밍하는 방식

코드를 추상화해 직관적으로 사고할 수 있다. 현실 세계의 객체를 유연하게 표현할 수 있고 각각의 특성을 가지고 있어 특정 기능을 수행할 수 있다. 

예를 들어 자동차는 객체이고 출발, 정지, 운행 등과 같은 기능을 수행할 수 있다.

 

객체지향 프로그래밍을 사용해야 하는 이유

만약 API 를 만들 때 복사-붙여넣기 방식으로 동일한 코드를 여러 군데 분산 시켜 놓는다면, 해당 로직을 수행할 때 복사한 코드를 일일히 찾아가며 수정을 해야하는 상황이 발생한다. 

객체지향 프로그래밍을 사용한다면 코드 변경점이 발생하더라도 최소한의 수정으로 많은 시간을 절약할 수 있다. 

발생한 문제점을 빠르게 인식하고 어떤 코드에서 오류가 났는지 빠르게 찾아 고쳐 개발에 소요되는 시간을 최대한 줄일 수 있게 된다. 

객체지향 프로그래밍은 데이터와 프로세스를 하나의 단위로 처리하는 특성을 가지고 있기 때문에 코드를 수정할 때 어떤 코드에서 문제가 발생했는지 직관적으로 인지할 수 있으며 해당 로직을 수행하는 코드만 고치더라도 문제가 해결될 수 있는 장점이 있다.  

객체지향 프로그래밍은 의존성을 효율적으로 통제할 수 있는 다양한 방법을 제공함으로써 요구사항 변경에 좀 더 수월하게 대응할 수 있는 가능성을 높여준다. 

동작을 기준으로 프로그래밍을 진행하는 것보다 데이터를 중심으로 프로그래밍을 하게 되면 코드의 규모가 커지더라도 일관성을 유지하기 좋다. 

 

객체지향 설계 5원칙(SOLID)

객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙의 맨 앞단어를 하나씩 가져와 만든 것

프로그래머가 시간이 지나도 유지보수와 확장이 쉬운 시스템을 만들고자 할 때 사용한다. 

  • 단일 책임의 원칙(Single Responsibility Principle, SRP)
    1. 하나의 객체는 하나의 책임을 가져야 한다. 
    2. 클래스나 모듈을 변경할 이유가 단 하나뿐이어야 한다
// 사용자의 인증을 검증하는 책임을 가진 UserAuth 클래스
class UserAuth {
  // UserAuth 생성자
  // 유저의 정보를 멤버 변수에 할당
  constructor(user) {
    this.user = user
  }
  // 유저 인증 메소드, user 멤버변수를 기반으로 인증하기 때문에 아무런 인자를 받지 않는다.
  verifyCredentials() {
    // ...
  }
}

// 사용자의 설정을 변경하는 책임을 가진 UserSettings 클래스
class UserSettings {
  constructor(user) {
    // user 를 인자로 받지만 userAuth라는 인스턴스를 만들어서 멤버변수에 할당한다..
    this.userAuth = new UserAuth(user)
  }
  changeSettings(userSettings) {
    // 생성자에서 선언한 userAuth 객체의 메소드를 사용
    if (this.userAuth.verifyCredentials()) {
      // ...
    }
  }
}
  • 개방-폐쇄의 원칙(Open-Closed Principle, OCP)
    1. 소프트웨어 엔티티 또는 개체(클래스, 모듈, 함수)는 확장에는 열려 있으나  변경에는 닫혀 있어야 한다. 
    2. 즉, 소프트웨어 개체의 행위는 확장될 수 있어야 하지만 변경되어선 안된다. 
    3. 기존 코드에 영향을 주지않고, 소프트웨어에새로운 기능이나 구성요소를 추가할 수 있어야 한다.
    4. 어떤 기능을 추가하더라도 calculator 함수 코드를 수정하지 않고 기능하도록 만든 함수
// 전달받은 callBack 함수 인자를 통해 기능을 확장할 수 있게 한다.
function calculator(nums, callBack) {
  let result = 0
  for (const num of nums) {
    result = callBack(result, num)
  }
  return result
}

const add = (a, b) => a + b
const sub = (a, b) => a - b
const mul = (a, b) => a * b
const div = (a, b) => a / b
// 전달받은 함수를 callBack 함수로 전달
calculator([2, 5], add)
  • 리스코프 치환 원칙(Liskov substitution principle, LSP)
    1. 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다. 
    2. 부모 클래스와 자식 클래스가 있는 경우 서로 바꾸더라도 해당 프로그램에서 잘못된 결과를 도출하지 않는다. 
class Shape {
  // Rectangle과 Square의 부모 클래스를 정의한다.
  getArea() {
    // getArea는 빈 메소드로 정의
  }
}

class Rectangle extends Shape {
  // Rectangle은 Shape를 상속받는다.
  constructor(width = 0, height = 0) {
    // 직사각형의 생성자
    super()
    this.width = width
    this.height = height
  }

  getArea() {
    // 직사각형의 높이와 너비의 결과값을 조회하는 메소드
    return this.width * this.height
  }
}

class Square extends Shape {
  // Square는 Shape를 상속받는다.
  constructor(length = 0) {
    // 정사각형의 생성자
    super()
    this.length = length // 정사각형은 너비와 높이가 같이 깨문에 width와 height 대신 length를 사용한다.
  }

  getArea() {
    // 정사각형의 높이와 너비의 결과값을 조회하는 메소드
    return this.length * this.length
  }
}

const rectangleArea = new Rectangle(7, 7) // 49
  .getArea() // 7 * 7 = 49
const squareArea = new Square(7) // 49
  .getArea() // 7 * 7 = 49

 

  • 인터페이스 분리 원칙(Interface segregation principle, ISP)
    1. 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다. 
    2. 클라이언트가 필요로 하지 않는 기능을 가진 인터페이스에 의존해서는 안되고, 최대한 인터페이스를 작게 유지해야 한다. 
  • 의존성 역전 원칙(Dependency Inversion Principle, DIP)
    1. 추상화에 의존해야 하고 구체화에 의존해선 안된다. 
    2. 높은 계층의 모듈(도메인)이 저수준의 모듈(하부구조)에 의존해서는 안된다. 
    3. 추상화를 하지 않고 고수준 계층의 모듈이 저수준 계층의 모듈을 의존하고 있다면 사소한 코드 변경에도 고수준 계층의 코드를 변경해야 할 것이고 소모되는 개발 코스트 또한 증가할 것이다.

'JavaScript' 카테고리의 다른 글

Closure(클로저)  (0) 2023.07.31
도메인(Domain)  (0) 2022.12.10
객체지향(Object-Oriented)  (0) 2022.12.10
JavaScript, 비동기함수 와 await(async/await)  (0) 2022.11.25
JavaScript, 콜백함수와 Promise  (1) 2022.11.25