TIL

내일배움캠프 15주차 수요일 TIL

news0516 2025. 2. 6. 03:25

 


1. OOP 기본 개념 이해 

1.1. 객체 (Object) 

현실 세계의 물체나 개념을 소프트웨어로 표현한 것. 예시: 자동차, 사람 등.


1.2. 클래스 (Class) 

객체를 생성하기 위한 설계도. 객체의 속성과 메서드를 정의. 예시: class Car { ... }


1.3. 속성 (Attribute) 

객체가 가지는 데이터. 객체의 상태를 나타냄. 예시: 자동차의 색상, 속도.


1.4. 메서드 (Method) 

객체가 수행할 수 있는 행동. 객체의 기능을 정의. 예시: start(), stop() 같은 함수.

더보기

클래스 (Class): 공장
객체 (Object): 자동차
속성 (Attribute): 자동차의 특성(색상, 모델, 연비 등)
메서드 (Method): 자동차의 기능(시동, 가속, 제동 등)


아하!



OOP를 적용하는 이유


객체 지향 프로그래밍(OOP)을 활용하면, 코드의 구조를 더 체계적으로 정리하고,
유지보수와 확장성이 뛰어난 '좋은 설계'를 지향할 수 있다



좋은 설계란?
1. 요구하는 기능을 정확히 수행하며,
2. 추후 변경, 확장이 용이하며,
3. 타인이 봐도 이해하기 쉬운, 즉 가독성이 높으며,
4. 쉽게 재사용이 가능한 설계 (ex:특정 기능 등 모듈화)


OOP는 여러 언어에서 지원하고 적용 가능하기 때문에 OOP를 배우면 다양한 언어 환경에서 좋은 설계를 하기 쉬워진다! 
"좋은 설계"를 더 깊이 이해하려면 SOLID 원칙같은 개념을 추가로 공부하면 좋습니다. 

 

SOLID 원칙 설명 초보자가 꼭 배워야 하는 이유
S 단일 책임 원칙 (SRP) 하나의 클래스는 하나의 책임만 가져야 한다 초보자들은 보통 "한 클래스에 모든 기능을 몰아넣는" 실수를 하므로, 이 원칙을 이해해야 함
O 개방-폐쇄 원칙 (OCP) 기존 코드를 수정하지 않고 확장할 수 있어야 한다 유지보수성을 높이는 핵심 원칙, 실전에서 매우 자주 사용됨
L 리스코프 치환 원칙 (LSP) 부모 클래스를 자식 클래스로 대체해도 정상 동작해야 한다 상속을 잘못 사용하면 코드가 깨질 수 있으므로 반드시 알아야 함
I 인터페이스 분리 원칙 (ISP) 클라이언트가 필요하지 않은 메서드에 의존하면 안 된다 불필요한 코드 의존성을 줄이는 중요한 원칙
D 의존성 역전 원칙 (DIP) 구체적인 구현이 아닌, 추상화에 의존해야 한다 유지보수성




 





맥도날드 개장!

맥도날드 개장을 위해서는 햄버거 , 즉 객체가 필요함

백종원의 솔루션을 거쳐 딱 한개의 햄버거만 필요하다면 이렇게 코딩해도 문제 x

const hamburger1 = {
	name: "빅맥",
	price: 5800
	calorie: 600,
}




현실

const hamburger1 = {
	name: "배토디",
	price: 5800
	calorie: 600,
}
const hamburger2 = {
	name: "슈비버거",
	price: "5800"
	calorie: 600,
}
const hamburger3 = {
	name: "빅맥",
	price: 7400
	calorie: 700,
}
const hamburger4 = {
	name: "상스치콤",
	price: 11900,
	calorie: 1100
}
........


같은 코드의 반복 >> 노가다
오타, 누락등의 실수 가능성
새로운 속성 추가 시 또 노가다

>> 햄버거 공장 개념의 Class만 추가해줘도 쉽게 햄버거, 즉 객체를 쉽게 생성하고 업데이트 할 수 있다.

공장을 통해 햄버거(객체) 양산 중...

class Hamburger = {
	constructor(name, price, calorie){
		this.name = name;
		this.price = price;
		this.calorie = calorie;
	}
}

const hamburger1 = new Hamburger("배토디", 5800, 600)
const hamburger2 = new Hamburger("슈비버거", 6500, 900)
const hamburger3 = new Hamburger("빅맥", 7400, 700)
const hamburger4 = new Hamburger("상스치콤", 11900, 1100)
...



2. OOP의 핵심 원칙

2.1. 캡슐화 (Encapsulation)

객체 내부의 세부 정보를 감추고, 외부에서 접근할 수 없도록 제한
장점: 데이터 보호, 코드 유지보수 용이성

class Hamburger {
  #price; // private 속성

  constructor(name, price) {
    this.name = name;
    this.#price = price;
  }

  getPrice() {
    return this.#price; // 허용된 메서드를 통해서만 접근 가능
  }

  setPrice(newPrice) {
    if (newPrice > 0) {
      this.#price = newPrice;
    } else {
      console.log("가격은 0원 이상이어야 합니다!");
    }
  }
}



const hamburger1 = new Hamburger("빅맥", 7000);

console.log(hamburger1.price) // undefined

console.log(hamburger1.getPrice()); //  7000

hamburger1.setPrice(10000); 
console.log(hamburger1.getPrice()); //  10000



2.2. 상속 (Inheritance)

기존 클래스의 속성과 메서드를 새로운 클래스가 물려받는 것
장점: 코드 재사용성 증가, 코드 중복 감소, 자식클래스에서 새롭게 확장 가능

class Hamburger {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }

  getInfo() {
    return `${this.name}: ${this.price}원`;
  }
}

// 자식 클래스: 치즈버거
class CheeseBurger extends Hamburger {
  constructor(price) {
    super("치즈버거", price); // 부모 클래스 속성 상속
    this.extraCheese = true;
  }

  addCheese() {
    console.log(`${this.name}에 치즈 추가`);
  }
}

const cheeseBurger = new CheeseBurger(8000);
console.log(cheeseBurger.getInfo()); // "치즈버거: 8000원"
cheeseBurger.addCheese(); // "치즈버거에 치즈 추가!"



2.3. 추상화 (Abstraction)

공통된 특성을 모아 상위 개념으로 정의하고 불필요한 세부 사항 생략
장점: 복잡성 감소, 시스템 이해 용이

// 추상 클래스
class Hamburger {
  constructor(name, price) {
    if (new.target === Hamburger) {
      throw new Error("추상 클래스는 직접 생성할 수 없습니다!");
    }
    this.name = name;
    this.price = price;
  }

  getInfo() {
    return `${this.name}: ${this.price}원`;
  }


}

// 자식 클래스: 치즈버거
class CheeseBurger extends Hamburger {
  constructor(price) {
    super("치즈버거", price);
    // 부모 클래스의 생성자를 super를 통해 호출
  }
  ...
  // 메서드 추가 가능 (확장 가능)
}

const cheeseBurger = new CheeseBurger(8500);
console.log(cheeseBurger.getInfo()); // "치즈버거: 8500"



2.4. 다형성 (Polymorphism)

동일한 메서드가 객체에 따라 다르게 동작하는것
장점: 유연한 코드 작성, 다양한 객체 간의 상호작용 용이

class Hamburger {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }

  getInfo() {
    return `${this.name}: ${this.price}원`;
  }
}

class CheeseBurger extends Hamburger {
  getInfo() {
    return `${this.name} (치즈 추가) - 가격: ${this.price}원`;
  }
}
// 부모 클래스와 같은 이름의 메서드를 재정의하여 사용 >> 오버라이딩

class SpicyBurger extends Hamburger {
  getInfo() {
    return `${this.name} (매운맛 추가) - 가격: ${this.price}원`;
  }
}

// 다양한 햄버거 객체 생성
const hamburger1 = new CheeseBurger("치즈버거", 8500);
const hamburger2 = new SpicyBurger("매운버거", 9000);


// 같은 메서드지만, 서로 다르게 동작함
console.log(hamburger1.getInfo()); // 치즈버거 (치즈 추가) - 가격: 8500원
console.log(hamburger2.getInfo()); // 매운버거 (매운맛 추가) - 가격: 9000원

 

더보기

OOP 핵심 원칙

키워드

예시

캡슐화

중요한 건 숨기고,
필요한 기능만 제공

주민번호 뒷자리는 비밀,
앞자리는 오픈! 

상속

기본 기능을 물려받아 확장

엄마 요리에, 몰래 MSG 추가

추상화

필요한 기능만 보여주고,
복잡한 건 감춤

버튼 딸깍으로 리모콘 사용,
내부 시스템은 모름

다형성

같은 요청이라도,
상황에 따라 다르게 동작

게임패드 X버튼을 눌러도,
철권에서는 펀치 \ 피파에서는 슛

 
아하!

 

3. 다형성, 오버 라이딩과 오버 로딩

class Hamburger {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }

  // 오버로딩처럼 동작하는 getInfo()
  getInfo(showPrice) {
    if (showPrice) {
      return `${this.name}: ${this.price}원`;
    }
    return `${this.name}`;
  }
}

class CheeseBurger extends Hamburger {
  getInfo(showPrice, hasCheese) {
    if (hasCheese) {
      return `${this.name} (치즈 추가) - ${showPrice ? `가격: ${this.price}원` : ""}`;
    }
    return super.getInfo(showPrice);
  }
}

class SpicyBurger extends Hamburger {
  getInfo(showPrice, isSpicy) {
    if (isSpicy) {
      return `${this.name} (매운맛 추가) - ${showPrice ? `가격: ${this.price}원` : ""}`;
    }
    return super.getInfo(showPrice);
  }
}

// 다양한 햄버거 객체 생성
const hamburger1 = new CheeseBurger("치즈버거", 8500);
const hamburger2 = new SpicyBurger("매운버거", 9000);

// 같은 메서드(getInfo)지만 인자 개수에 따라 다르게 동작
console.log(hamburger1.getInfo(true)); //  "치즈버거: 8500원"
console.log(hamburger1.getInfo(true, true)); //  "치즈버거 (치즈 추가) - 가격: 8500원"
console.log(hamburger1.getInfo(false, true)); //  "치즈버거 (치즈 추가)"

console.log(hamburger2.getInfo(true)); //  "매운버거: 9000원"
console.log(hamburger2.getInfo(true, true)); //  "매운버거 (매운맛 추가) - 가격: 9000원"
console.log(hamburger2.getInfo(false, true)); //  "매운버거 (매운맛 추가)"



이러한 부모 클래스의 메서드를 같은 이름으로 사용하지만, 자식클래스에서 재정의하여 사용하는 것을 오버라이딩
>> 기존의 메서드를 재정의 한다.
>>> 상속 관계에서 동일한 메소드 명으로 새로운 기능을 동작하고 싶을 경우 사용

같은 메서드/생성자를 사용하지만 매개변수 개수나 값에 따라 다르게 동작하도록 구현하는 것을 오버로딩
>> 같은 이름의 메서드를 같은 클래스 내에서 여러 개 정의한다.
>>> 같은 클래스 or 인터페이스에서 동일한 메소드 명으로 다양한 기능을 동작하게 하고 싶을 경우 사용

오버로딩 또한 같은 getInfo 메서드를 사용하지만 다른 결과를 도출하고 있기 때문에, 오버라이딩과 오버로딩 모두 다형성을 구현하는 방식으로 볼 수 있다.

이러한 다형성을 활용하여 유연성 높고 확장성, 유지보수성이 높은 코드 작성이 가능해집니다.


 



사진 제공 : 오혜성