노드 심화_1

·2022년 12월 26일
0

노드 심화

목록 보기
1/9

객체 지향 (Object-Oriented)

; 소프트웨어의 핵심을 기능이 아닌 객체로 삼으며 '누가 어떠한 일을 할 것 인가?' 에 초점을 맞춤

즉, 객체를 도출하고 각각의 역할을 정의하는 것에 초점을 맞춤

그렇다면 객체 지향적인 소프트웨어는 어떻게 구분할 수 있을까?

절차지향적인 소프트웨어와 객체지향적인 소프트웨어를 구분하는 방법은 아래의 기준을 만족할 경우 객체지향, 만족하지 않으면 절차지향적인 성격을 가진다.

  • 캡슐화, 다형성, 클래스 상속을 지원하는가?
  • 데이터 접근 제한을 걸 수 있는가?

1) 캡슐화

개념적이나 물리적으로 객체 내부의 세부적인 사항을 감추는 것

  • 캡슐화의 목적은 변경하기 쉬운 객체를 만드는 것
    캡슐화를 통해 객체 내부의 접근을 제한하면 객체와 객체 사이의 결합도를 낮출 수 있기 때문에 설계를 좀 더 쉽게 변경할 수 있게 됨

Javascript 클래스는 멤버 변수를 숨길 수 없습니다. 그래서 개발자들은 멤버 변수 앞에 _를 붙여 클래스 내부의 변수를 숨긴 것 “처럼" 표시하겠다는 규칙을 만들었습니다.

하지만 Javascript를 실행했을 때에는 클래스의 멤버 변수가 숨겨지지 않으니, 이번 예제는 Typescript로 확인해보도록 하겠습니다.

/** Encapsulation **/
class User {
  private name: string;
  private age: number;

  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(28);
console.log(user.getName()); // 이용우
console.log(user.getAge()); // 28
console.log(user.name); // Error: User클래스의 name 변수는 private로 설정되어 있어 바로 접근할 수 없습니다.

User 클래스를 선언하고 내부에는 name, age 멤버 변수를 초기화 하였습니다.

여기서는 특별하게 Private라는 접근 제한자(Access modifier)를 사용하고 있는데요, 인스턴스 내부에서만 해당 변수에 접근이 가능하도록 제한하는 문법 입니다. 기존에 Javascript에서는 존재하지 않았지만 Typescript에서 제공하는 문법입니다.

접근 제한자에 대해 자세히 알고 싶다면 여기를 클릭하세요!

여기서 User 클래스의 name, age 멤버 변수는 클래스 외부에서는 어떠한 방법으로도 직접 접근을 할 수 없습니다. 오로지 setter만 변수를 변경할 수 있고, getter만 변수를 조회할 수 있게 되었습니다.

2) 상속 (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

Mother 부모 클래스를 상속받은 Child 자식 클래스에서 name, age 멤버 변수를 직접 접근하여 호출하고, Mother 부모 클래스에서 정의된 getTech() 메소드를 호출하여 Child 자식 클래스에서 사용할 수 있게 되었습니다.

3) 추상화 (Abstraction)

; 객체에서 공통된 부분을 모아 상위 개념으로 새롭게 선언하는 것

즉, 불필요한 부분을 생략하고 객체 속성 중 공통적이고 중요한 것에만 중점을 두어 모델화 하는 것

클래스를 설계할 때 공통적으로 묶일 수 있는 기능을 추상화추상 클래스인터페이스로 모델링해서 향후 다형성(Polymorphism)으로 확장할 수 있도록 설계

여기서 인터페이스(Interface)란 클래스 정의할 때 메소드속성정의하여 인터페이스에 선언된 프로퍼티 또는 메소드의 구현을 강제하여 코드의 일관성을 유지할 수 있도록 만듦
인터페이스에 대해 자세히 알고 싶다면 여기를 클릭하세요!

/** Abstraction **/
interface Human {
  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.setName("이용우"); // Employee 클래스의 name을 변경하는 setter
console.log(employee.getName()); // Employee 클래스의 name을 조회하는 getter

Employee 클래스는 Human 인터페이스에서 상속받은 name 멤버 변수와 setName, getName 추상 메소드를 강제로 구현하게 되었습니다.

4) 다형성 (Polymorphism)

; 객체(클래스)연산을 수행하게 될 때 하나의 행위에 대해 각 객체가 가지고 있는 고유한 특성으로 다른 여러 형태로 재구성 되는 것

즉, 동일한 메소드의 이름을 사용하지만 메소드에 대해 클래스마다 다르게 구현되는 개념

다형성을 통해 역할(인터페이스)과 구현을 분리해서 오버라이딩(Overriding)을 통해 서비스의 구현기능을 유연하게 변경, 확장이 가능

/** Polymorphism **/
class Employee {
  constructor(name) { this.name = name; }

  buy() { console.log(`${this.constructor.name} 클래스의 ${this.name}님이 물건을 구매하였습니다.`); }
}

class User {
  constructor(name) { this.name = name; }

  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 polymorphismArray = [employee1, employee2, user1, user2];
// polymorphismArray에 저장되어 있는 Employee, User 인스턴스들의 buy 메소드를 호출합니다.
polymorphismArray.forEach((polymorphism) => polymorphism.buy());

// Employee 클래스의 이용우님이 물건을 구매하였습니다.
// Employee 클래스의 김창환님이 물건을 구매하였습니다.
// User 클래스의 이태강님이 물건을 구매하였습니다.
// User 클래스의 김민수님이 물건을 구매하였습니다.

polymorphismArray.forEach() 에서 polymorphismEmployee 또는 User 클래스를 가리키고 있습니다.

해당 반복문에서 매번 buy 메소드를 호출하는것은 동일하지만, EmployeeUser 클래스에서 동작하는 buy 메소드는 다른 행위를 하고 있는 것을 확인!

6) 의존성 (Dependency)

객체(모듈 및 클래스)들이 협력하는 과정 속에서 해당 객체들이 다른 객체를 의존하게 되는 정도

7) 결합도 (Coupling)

다른 모듈에 대해 얼마나 많은 의존성을 가지고 있는지

  • 객체 사이의 의존성이 과한 경우를 가리켜 결합도가 높다고 말한다.
  • 객체들이 합리적인 수준으로 의존할 경우에는 결합도가 낮다고 말한다.
  • 두 객체 사이의 결합도가 높으면 높을수록 함께 변경될 확률도 높아지기 때문에 변경하기 어려워진다.
  • 따라서 설계의 목표는 객체 사이결합도를 낮춰 변경이 용이한 설계를 만드는 것이어야 한다.

8) 응집도 (Cohesion)

모듈에 포함된 내부 요소들이 각각 연관되어 있는 관계의 정도

  • 밀접하게 연관된 작업만을 수행하고 연관성 없는 작업은 다른 객체에 위임하는 객체를 가리켜 응집도가 높다고 말한다.
  • 1개의 메소드가 내부에서 변수를 많이 사용할 수록 해당 메소드클래스응집도가 높아지게됩니다.
  • 자신의 데이터를 스스로 처리하는 자율적인 객체를 만들면 결합도를 낮출 수 있을뿐더러 응집도를 높일 수 있다.

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

1) 프로그래밍 패러다임

; 무엇을 해야 할지를 말하기보다는 무엇을 해서는 안되는지를 말해줌

프로그래밍 패러다임에는 대표적으로 3가지가 존재합니다.

  1. 구조적 프로그래밍 (Structured Programming)
    ; 기능 중심 개발, 가장 처음으로 적용된 패러다임
  2. 객체 지향 프로그래밍 (Object-Oriented Programming, OOP)
    ; 프로그램 처리 단위가 객체, '현실 세계를 모델링' 하는 패러다임
  3. 함수형 프로그래밍 (Functional Programming)
    ; 함수 중심 개발, 가장 처음 만들어졌지만 최근 겨우 도입

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

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

  • 객체는 어떠한 특성을 가지고 있으며 특정 기능을 수행할 수 있습니다.
    ex) 자동차는 객체이고 출발, 정지, 운행 및 제동과 같은 기능을 수행할 수 있습니다.

3) 객체 지향 프로그래밍 사용 이유

객체지향 프로그래밍은 데이터와 프로세스를 하나의 단위로 처리하는 특성을 가지고 있기 때문에 코드를 수정해야할 때 어떤 코드에서 문제가 발생했는지 개발자들이 직관적으로 인지할 수 있으며 여러곳에 분산된 모든 코드를 수정해야하는 것이 아닌 해당 로직을 수행하는 코드만 수정하더라도 문제가 해결될 수 있습니다.

4) 좋은 설계란?

  • 좋은 설계요구하는 기능온전히 수행하면서 추후의 변경을 매끄럽게 수용할 수 있는 설계
  • 변경 가능한 코드이해하기 쉬운 코드
    만약 코드를 변경해야 하는데 그 코드를 이해할 수 없다면 변경에 유용하더라도 코드를 수정하겠다는 마음이 선뜻 들지는 않는다.
  • 변경하기 쉬운 설계는 한 번에 하나의 클래스만 변경할 수 있는 설계
  • 훌륭한 객체지향 설계의 핵심은 캡슐화를 이용해 의존성을 적절히 관리함으로써 객체 사이의 결합도를 낮추는 것
profile
개발자가 되는 과정

0개의 댓글