말 그대로 강한 결합이라는 뜻으로 클래스와 객체가 서로 의존 하고 있다는 뜻이며 느슨한 결합의 반대 개념이다!
예제 코드를 통해 알아보자
import { CashService } from "./services/cash.service.js";
import { ProductService } from "./services/product.service.js";
export class ProductController {
buyProduct = (req, res) => {
console.log(req.body)
// 1. 가진돈 검증하는 코드 (대략 10줄 정도 => 2줄)
const cashService = new CashService();
const hasMoney = cashService.checkValue();
// 2. 판매여부 검증하는 코드 (대략 10줄 정도 => 2줄)
const productService = new ProductService();
const isSoldOut = productService.checkSoldOut();
// 3. 상품 구매하는 코드
if (hasMoney && !isSoldOut) {
res.send("상품 구매 완료!!");
}
};
refundProduct = (req, res) => {
// 1. 판매여부 검증하는 코드 (대략 10줄 정도 => 2줄)
const productService = new ProductService();
const isSoldOut = productService.checkSoldOut();
// 2. 상품 환불하는 코드
if (isSoldOut) {
res.send("상품 환불 완료!!");
}
};
}
위 예제 코드를 살펴보면 ProductController라는 클래스가
CashService와 ProductService에의해 동작 되는 것을 볼수 있는데
이것을 ProductController가 CashService와ProductService에 의존하고 있다,강하게 결합되어있다(tight-coupling) 라고 볼 수 있다
위에서 살펴본 강한 결합의 반대적 개념으로
느슨한 결합(Loose Coupling)
이란 "객체들 간에 결합이 되어있긴 하는데 헐겁게 됐다."로 해석할 수 있으며,
다른 클래스를 직접적으로 사용하는 클래스의 의존성을 줄이는 것
입니다.
예제 코드를 통해 알아보자!
// const express = require('express') // 옛날방식 => commonjs
import express from "express"; // 요즘방식 => module
import { ProductController } from "./mvc/controllers/product.controller.js";
import { CouponController } from "./mvc/controllers/coupon.controller.js";
import { CashService } from "./mvc/controllers/services/cash.service.js";
import { ProductService } from "./mvc/controllers/services/product.service.js";
import { PointService } from "./mvc/controllers/services/point.service .js";
const app = express(); // express에 있는 기능을 app에 담아줌 - > api만드는 기능 생성
const cashService = new CashService()
const productService = new ProductService()
const pointService = new PointService()
// 상품 API
const productController = new ProductController(pointService, productService) // 느슨한 결합 loose-coupling
app.post("/products/buy", productController.buyProduct); // 상품 구매하기 API
app.post("/products/refund", productController.refundProduct); // 상품 환불하기 API
// 쿠폰(상품권) API
const couponController = new CouponController(cashService)
app.post("/coupons/buy", couponController.buyCoupon)
// 게시판 API
// app.get("/boards/...")
app.listen(3000); // 리슨 기다린다 (포트번호)
위 코드를 살펴보면 강한 결합이 되어있던 CashService와 ProductService를 객체로 선언 후 ProductController에 생성자 주입을 통해 전달인자로 넣어준것을 확인 할 수 있다
즉, 의존성 주입을 해주었다고 볼 수 있다
=== 의존성 주입으로 발생하는 장점! ===
1. new 한 번으로 모든 곳에서 재사용 가능 -> 싱글톤 패턴
2. 컨트롤러 코드를 수정하지 않고, 한꺼번에 변경 가능
3. 현금으로 결제하던 것을 포인트로 변경 가능
[부가설명]
1. ProductController가 CashService에 의존하고 있음(의존성 : CashService)
=> 이 상황을 "강하게 결합되어있다" 라고 표현
=> tight-coupling
2. 이를 개선하기 위해서 "느슨한 결합"으로 변경할 필요가 있음
=> loose-coupling
=> (의존성: CashService)을 밖에서 집어 넣어 주기
=> 이를 "의존성 주입"이라고 함
=> 영어로는 "Dependency-Injection", 줄여서 DI
=> 이 역할을 대신 해주는 Nestjs 기능 : IoC 컨테이너 (알아서 new 해서 넣어줌 즉, DI 해줌)
=> 영어로는 "Inversion-Of-Control"
3. "의존성주입"으로 "싱글톤패턴" 구현 가능해짐
=> Q) "의존성주입"이면, "싱글톤패턴"인가?
=> A) 그건 아님!
타입스크립트는 자바스크립트에 타입을 부여한 언어입니다.
자바스크립트의 확장된 언어라고 볼 수 있습니다.
타입스크립트는 자바스크립트와 달리 브라우저에서 실행하려면 파일을 한번 변환해 주어야 합니다.
이 변환 과정을 우리는 컴파일(complile) 이라고 부릅니다.
타입스크립트는 아래 2가지 관점에서 자바스크립트 코드의 품질과 개발 생산성을 높일 수 있습니다.
// 타입추론 let str : string = "안녕하세요" :string 생략해도 처음 할당된 값에 의해 타입추론을 함
let str = "안녕하세요"
// str = 3 숫자타입 할당 불가
// 타입명시
let str2: string = "반갑습니다"
// 타입명시가 필요한 상황 number string 둘다 사용가능
let numStr: number | string = 1000
numStr = "1000원"
// 숫자타입
let num: number = 10
// 불린타입
let bool: boolean = true
bool = false
// bool = "false" 불가능
// 배열타입
let fff: number[] = [1, 2, 3, 4, 5]
let ggg: string[] = ["철수", "영희" , "훈이"]
let hhh: (string | number)[] = ["철수", "영희", 10] // 타입을 추론해서 어떤타입을 사용하는지 알아보기!
// 객체타입
const profile = {
name : "철수",
age : 8,
school : "다람쥐초등학교"
}
profile.name = "훈이" // 타입 추론으로는 이것만 가능
// profile.age = "8살" number타입이기때문에 string 불가
// profile.hobby = "수영" 타입 추론시 hobby 존재하지 않기 때문에 불가능
interface IProfile {
name : string,
age : number | string,
school: string,
hobby: string
}
// const profile2: IProfile = { hobby 속성이 없어서 에러
// name : "철수",
// age : 8,
// school : "다람쥐초등학교"
// }
interface IProfile2 {
name : string,
age : number | string,
school: string,
hobby?: string
}
const profile2: IProfile2 = { // ? 사용하여 hobby없어도 가능
name : "철수",
age : 8,
school : "다람쥐초등학교"
}
// any 타입
let qqq: any = "철수" // 자바스크립트와 동일
qqq = 123
qqq = true
// 함수타입 => 어디서 누가 어떻게 호출할지 모르므로, 타입추론 할 수 없음(반드시, 타입명시 필요!!)
function add(num1: number, num2: number, unit: string): string {
return num1 + num2 + unit
}
const result = add(1000,2000, "원") // 결과의 리턴 타입도 예측 가능!!
const add2 = (num1: number, num2: number, unit: string): string => {
return num1 + num2 + unit
}
const result2 = add2(1000,2000, "원") // 결과의 리턴 타입도 예측 가능!!
데코레이터는 실행하려는 사용자가 구조를 수정하지 않고 기존 객체에 새로운 기능을 추가할 수 있도록 하는 디자인 패턴입니다.
일반적으로 데코레이트 하려는 함수의 정의 전에 호출됩니다. 데코레이터는 함수를 인수로 얻고 대가로 새로운 함수로 돌려주는 cllable(전달받은 object 인자가 호출 가능한지를 판단)구조 입니다.
자바스크립트를 확장한 언어라고 할 수 있는 타입 스크립트에서는 실험적인 기능으로 데코레이터를 제공하고 있습니다. 따라서 커맨드 라인이나 tsconfig.json
에서 experimentalDecorators
옵션을 추가해 줘야 합니다.
데코레이터는 앞에서 말한 것처럼 “클래스”, “메서드”, “접근자”, “프로퍼티”, “파라미터”에 적용할 수 있습니다.
function Controller(test: any) {
console.log("==============")
console.log(test)
console.log("==============")
}
// 데코레이터 -> 함수의 전달인자로 클래스를 전달해줌
@Controller
class CatsController {
}
작동 원리를 알아보기 위해 예제코드를 작성하여 확인해 보자!
public
class Monster2 {
// power; => public, private, protected, readonly 중 1개라도 있으면 생략 가능
constructor(public power) {
// this.power = power => public, private, protected, readonly 중 1개라도 있으면 생략 가능
}
attack1 = () => {
console.log("공격하자!!");
console.log("내 공격력은 " + this.power + "야!!!"); // 내부에서 접근 가능
this.power = 30 // 내부에서 변경 가능
};
}
class miniMonster2 extends Monster2 {
attack2 = () => {
console.log("공격하자!!");
console.log("내 공격력은 " + this.power + "야!!!"); // 자식이 접근 가능
this.power = 30 // 자식이 수정 가능
};
}
const myMonster22 = new miniMonster2(20)
myMonster22.attack1()
myMonster22.attack2()
console.log(myMonster22.power) // 외부에서 접근 가능
myMonster22.power = 50 // 외부에서 수정 가능
private
class Monster2 {
// power; => public, private, protected, readonly 중 1개라도 있으면 생략 가능
constructor(private power) {
// this.power = power => public, private, protected, readonly 중 1개라도 있으면 생략 가능
}
attack1 = () => {
console.log("공격하자!!");
console.log("내 공격력은 " + this.power + "야!!!"); // 내부에서 접근 가능
this.power = 30 // 내부에서 변경 가능
};
}
class miniMonster2 extends Monster2 {
attack2 = () => {
console.log("공격하자!!");
console.log("내 공격력은 " + this.power + "야!!!"); // 자식이 접근 불가
this.power = 30 // 자식이 수정 불가
};
}
const myMonster22 = new miniMonster2(20)
myMonster22.attack1()
myMonster22.attack2()
console.log(myMonster22.power) // 외부에서 접근 불가
myMonster22.power = 50 // 외부에서 수정 불가
protected
class Monster2 {
// power; => public, private, protected, readonly 중 1개라도 있으면 생략 가능
constructor(protected power) {
// this.power = power => public, private, protected, readonly 중 1개라도 있으면 생략 가능
}
attack1 = () => {
console.log("공격하자!!");
console.log("내 공격력은 " + this.power + "야!!!"); // 내부에서 접근 가능
this.power = 30 // 내부에서 변경 가능
};
}
class miniMonster2 extends Monster2 {
attack2 = () => {
console.log("공격하자!!");
console.log("내 공격력은 " + this.power + "야!!!"); // 자식이 접근 가능
this.power = 30 // 자식이 수정 가능
};
}
const myMonster22 = new miniMonster2(20)
myMonster22.attack1()
myMonster22.attack2()
console.log(myMonster22.power) // 외부에서 접근 불가
myMonster22.power = 50 // 외부에서 수정 불가
readonly
class Monster2 {
// power; => public, private, protected, readonly 중 1개라도 있으면 생략 가능
constructor(readonly power) {
// this.power = power => public, private, protected, readonly 중 1개라도 있으면 생략 가능
}
attack1 = () => {
console.log("공격하자!!");
console.log("내 공격력은 " + this.power + "야!!!"); // 내부에서 접근 가능
this.power = 30 // 내부에서 변경 불가
};
}
class miniMonster2 extends Monster2 {
attack2 = () => {
console.log("공격하자!!");
console.log("내 공격력은 " + this.power + "야!!!"); // 자식이 접근 가능
this.power = 30 // 자식이 수정 불가
};
}
const myMonster22 = new miniMonster2(20)
myMonster22.attack1()
myMonster22.attack2()
console.log(myMonster22.power) // 외부에서 접근 가능
myMonster22.power = 50 // 외부에서 수정 불가
private readonly
class Monster2 {
// power; => public, private, protected, readonly 중 1개라도 있으면 생략 가능
constructor(private readonly power) {
// this.power = power => public, private, protected, readonly 중 1개라도 있으면 생략 가능
}
attack1 = () => {
console.log("공격하자!!");
console.log("내 공격력은 " + this.power + "야!!!"); // 내부에서 접근 가능
this.power = 30 // 내부에서 변경 불가
};
}
class miniMonster2 extends Monster2 {
attack2 = () => {
console.log("공격하자!!");
console.log("내 공격력은 " + this.power + "야!!!"); // 자식이 접근 불가
this.power = 30 // 자식이 수정 불가
};
}
const myMonster22 = new miniMonster2(20)
myMonster22.attack1()
myMonster22.attack2()
console.log(myMonster22.power) // 외부에서 접근 불가
myMonster22.power = 50 // 외부에서 수정 불가
public => 접근,수정 어디서든지 가능
private => 내부에서만 접근,수정 가능
protected => 내부,자식만 접근,수정 가능
readonly => 접근만 어디서든지 가능
private readonly => 내부에서 접근만 가능!
오늘의 내용들은 next.js를 배우기 전 알아두면 좋은 이론들을 배워봤다
자바스크립트에서 false,'false' 이 데이터 타입 차이로 로직이 망가져 본적이 있는데 타입스크립트는 사전에 방지 할 수 있어 아주 좋은 것 같다! 내일 배울 nest.js를 이해하기 쉽게 오늘의 내용을 한번 더 복습하자~~🔥🔥🔥