내일배움캠프 TIL (221227): 객체지향 프로그래밍의 법칙 / 3-Layered Architecture(3계층 아키텍처) express에서 구현하기

Jiumn·2022년 12월 27일
0

객체지향 프로그래밍을 글로 배웠어요...
직접 코드를 짜면서 가슴이 아닌 머리로 이해하는 날이 오길 바라며 정리해보는 글

객체 지향(Object-Oriented)이란

  • 객체를 만들고 각각의 역할을 정의하는 것에 초점을 맞춤
  • 각자의 책임과 권한을 가진 객체들이 기능을 수행하도록 시스템을 개발하는 것

객체 지향적인 소프트웨어의 특징

대표적으로는 다음과 같은 특징이 있음

  • 캡슐화
  • 다형성
  • 클래스 상속
  • 데이터 접근 제한

캡슐화 (Encapsulation)

  • 객체 내부의 세부적인 사항을 감추는 것
  • 정보 은닉이 목적
  • 객체 내부의 접근을 제한하기 위해 접근 제한자 Private를 사용함

상속 (Inheritance)

  • 상위 클래스의 특징을 하위 클래스가 물려받는 것
  • 코드의 재사용성 증대를 위함
  • 상위 클래스만 수정하므로 일관성을 유지할 수 있음

추상화 (Abstraction)

  • 객체들의 공통적인 특성을 묶어서 상위 개념으로 선언하는 것
  • 필요 없는 특성은 제거하는 과정
  • Interface를 통해 클래스를 정의할 때 메소드와 속성만 정의, 상속 받은 클래스에서 해당 메소드와 속성을 구현하지 않으면 에러 발생

다형성 (Polymorphism)

  • 다른 클래스가 동일한 메소드의 이름을 사용하더라도 클래스마다 다르게 구현되는 것

의존성 (dependency)

  • 어떤 객체가 변경될 때 그 객체의 의존하는 다른 객체도 변경될 수 있음

결합도 (Coupling)

  • 객체들 사이의 의존성의 정도
  • 의존성이 과하면 결합도가 높다고 표현
  • 합리적인 수준으로 의존하면 결합도가 낮다고 표현
  • 객체 사이의 결합도를 낮춰 변경이 용이한 설계를 만드는 것이 이상적

응집도 (Cohesion)

  • 한 객체 안에서는 연관된 작업만 수행하고 다른 작업은 다른 객체에 위임하는 것 → 응집도가 높다고 표현
  • 스스로 처리할 수 있는 메소드가 많음 → 자신의 데이터를 스스로 처리하는 자율적인 객체 → 결합도가 낮고 응집도가 높다

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

유지 보수와 확장이 쉬운 시스템을 만들기 위해 사용함

단일 책임의 원칙 (Single Responsibility Principle, SRP)

  • 하나의 객체는 단 하나의 책임을 가져야 함
  • 2가지 이상의 책임을 가진 객체는 다른 클래스로 분리해야 함

개방-폐쇄 원칙 (Open-Closed Principle, OCP)

  • 클래스, 모듈, 함수는 확장에는 열려 있지만 변경에는 닫혀 있어야 함
  • 즉, 기존 코드에 영향을 주지 않고 새로운 기능이나 구성 요소를 추가할 수 있어야 한다는 의미

리스코프 치환 원칙 (Liskov substitution principle, LSP)

  • 부모 클래스와 자식 클래스가 있는 경우 서로를 바꾸더라도 해당 프로그램에서 잘못된 결과를 도출하지 않는 것

인터페이스 분리 원칙 (Interface segregation principle, ISP)

  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다

의존성 역전 원칙 (Dependency Inversion Principle, DIP)

  • 고수준 계층의 모듈(도메인)은 저수준 계층의 모듈(하부구조)에 의존해서는 안된다

3계층 아키텍처

  • 폴더 구조 예시
프로젝트 명
├── app.js
├── config
│   └── config.json
├── controllers
│   └── posts.controller.js
├── migrations
│   └── 20220731133318-create-posts.js
├── models
│   ├── index.js
│   └── posts.js
├── repositories
│   └── posts.repository.js
├── routes
│   ├── index.js
│   └── posts.routes.js
├── services
│   └── posts.service.js
├── package.json
└── package-lock.json

express 컨트롤러와 라우터 연결하기

  • posts.router.js
const express = require("express");
const router = express.Router();

// postscontroller 모듈 불러오기 - 첫 글자 대문자로
const PostsController = require("../controllers/posts.controller");
// 불러온 postscontroller 모듈을 변수에 저장
const postsController = new PostsController();

// 라우터에서 컨트롤러의 메서드를 사용할 수 있음
router.get("/", postsController.getPosts);
router.post("/", postsController.createPosts);

// 모듈로 내보냄
module.exports = router;

express 모듈을 불러오고
router를 만든다

PostsController 모듈을 불러온 다음
postsController 변수에 저장한다

url '/'로 get 요청 시 postsController의 getPosts 메서드를 사용한다
url '/'로 post 요청 시 postsController의 creatPosts 메서드를 사용한다

router를 모듈로 내보낸다.

컨트롤러와 서비스 연결하기

  • posts.controller.js
const PostService = require("../services/posts.service");

// Post의 컨트롤러 역할을 하는 클래스
class PostsController {
  postService = new PostService(); // Post 서비스 클래스를 컨트롤러 클래스의 멤버변수로 할당한다.

  getPosts = async (req, res, next) => {
    // 서비스 계층에 구현된 findAllPost 로직을 실행한다.
    const posts = await this.postService.findAllPost();

    res.status(200).json({ data: posts });
  };

  createPost = async (req, res, next) => {
    const { nickname, password, title, content } = req.body;

    // 서비스 계층에 구현된 creatPost 로직을 실행한다.
    const createPostData = await this.postService.createPost(
      nickname,
      password,
      title,
      content
    );

    res.status(201).json({ data: createPostDats });
  };
}

module.exports = PostsController;

service 모듈을 불러온다

PostController 클래스를 선언해주고
PostService를 postService라는 멤버 변수로 저장한다

service 계층에 구현된 findAllPost() 로직을 실행하는 getPosts 메서드를 만들고
service 계층에 구현된 creatPost() 로직을 실행하는 getPostsData 메서드를 만든다

controller를 모듈을 내보낸다.

서비스와 레포지토리 연결하기

const PostRepository = require("../repositories/posts.repository");

class PostService {
  postRepository = new PostRepository();

  findAllPost = async () => {
    // 저장소에 데이터를 요청합니다.
    const allPost = await this.postRepository.findAllPost();

    // 호출한 post들을 가장 최신 게시글부터 정렬합니다.
    allPost.sort((a, b) => {
      return b.createAt - a.createdAt;
    });
    // 비즈니스 로직을 수행한 후 사용자에게 보여줄 데이터를 가공합니다.
    return allPost.map((post) => {
      return {
        postId: post.postId,
        nickname: post.nickname,
        title: post.title,
        createdAt: post.createAt,
        updatedAt: post.updatedAt,
      };
    });
  };

  createPost = async (nickname, password, title, content) => {
    // 저장소에 데이터를 요청한다.
    const createPostData = await this.postRepository.createPost(
      nickname,
      password,
      title,
      comment
    );

    // 비즈니스 로직을 수행한 후 사용자에게 보여줄 데이터를 가공합니다.
    return {
      postId: createPostData.null,
      nickname: createPostData.nickname,
      title: createPostData.title,
      content: createPostData.content,
      createAt: createPostData.createAt,
      updatedAt: createPostData.updatedAt,
    };
  };
}

module.exports = PostService;

이 정도 되면 공통점이 보인다.

router에서는 controller를 불러내고
controller에서는 service를 불러내고
service에서는 repository를 불러내는 방향으로 코드가 작성된다.

마지막으로 repository에서는 models을 불러온다.

profile
Back-End Wep Developer. 꾸준함이 능력이다.

0개의 댓글