현실세계의 개념을 객체로 표현해 코드의 재사용성과 유지보수성을 높이는 프로그래밍 방식
→ 일상적으로 사용하는 사물을 프로그램 안에서 하나의 객체로 만드는 것
예시로 들자면 자동차가 있다
자동차는 가장 기본적인 개념인데 이를 공통적으로 포함하는 클래스를 선언 가능합니다
class Car {
String color;
int speed;
void drive() {
// 운전하는 동작
}
void stop() {
// 정지하는 동작
}
}
이 클래스에는 모든 자동차의 속도 , 색상 , 운전 , 정지 같은 메서드가 들어갈 수 있습니다. 모든 자동차가 기능으로 가지는 기능입니다.
아우디와 벤츠는 자동차 클래스에서 파생된 자동차 모델입니다. 각각의 독특한 디자인과 특징을 추가할 수있습니다.
class Audi extends Car {
boolean quattro;
Audi() {
this.quattro = true;
}
}
class Benz extends Car {
boolean mbux;
Benz() {
this.mbux = true;
}
}
Car라는 모든 자동차의 기능을 정의하고 이를 extends를 통해 상속 받고 Car 객체의 메서드를 사용 가능하다
class Car {
#speed = 0;
setSpeed(value) {
this.#speed = value;
}
getSpeed() {
return this.#speed;
}
}
const myCar = new Car();
myCar.setSpeed(60);
console.log(myCar.getSpeed()); // 60
위 내용 처럼 프라이버시 문자열을 이용해서 하는것도 가능
객체지향의 특징으로는 4가지가 있다
객체에서 공통된 속성과 행위를 추출하는 기능
즉 아우디 벤츠 르노 → 자동차를 추출
불필요한 정보를 숨기고 중요한 정보만을 표현!!
즉 복잡한 구현 세부사항을 숨기는것에 초점을 둔다
결제하는 로직은 상당히 복잡하다
class PaymentProcessor {
processPayment(amount, cardInfo) {
this.#encryptCardInfo(cardInfo); // 카드 정보 암호화
this.#connectToGateway(); // 결제 게이트웨이 연결
this.#verifyAndProcess(amount); // 인증 및 결제 진행
console.log("결제가 완료되었습니다!");
}
#encryptCardInfo(cardInfo) {
// 카드 정보를 암호화하는 내부 로직 (숨겨진 세부사항)
}
#connectToGateway() {
// 결제 게이트웨이에 연결하는 로직 (숨겨진 세부사항)
}
#verifyAndProcess(amount) {
// 결제 인증 및 처리 로직 (숨겨진 세부사항)
}
}
데이터 구조와 데이터를 다루는 방법들을 결합 시켜 묶는 것 ( 변수와 함수를 하나로 묶는 것을 뜻함) 낮은 결합도를 유지할 수 있도록 설계하는 것
객체의 데이터와 메서드를 하나로 묶고 외부에서 필요한 부분만 접근 가능하도록 제한하는것
이를 통해 데이터의 무결성을 보호하고 객체의 내부 구현을 숨길 수 있다.
목적
class BankAccount {
#balance = 0; // 잔액은 private으로 설정
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
console.log(`입금 완료: ${amount}원, 현재 잔액: ${this.#balance}원`);
} else {
console.log("입금 금액은 양수여야 합니다.");
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
console.log(`출금 완료: ${amount}원, 현재 잔액: ${this.#balance}원`);
} else {
console.log("출금 금액이 잘못되었거나 잔액이 부족합니다.");
}
}
getBalance() {
return this.#balance;
}
}
이처럼 캡슐화는 객체 내부 데이터를 보호하고, 외부에는 필요한 메서드만 제공하여 안전하고 효율적인 코드를 작성할 수 있게 도와줍니다
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}이(가) 소리를 냅니다.`);
}
}
class Dog extends Animal {
speak() {
super.speak(); // 부모 클래스의 speak 메서드 호출
console.log(`${this.name}이(가) 멍멍! 짖습니다.`);
}
}
class Cat extends Animal {
speak() {
super.speak(); // 부모 클래스의 speak 메서드 호출
console.log(`${this.name}이(가) 야옹~ 소리를 냅니다.`);
}
}
class Cow extends Animal {
}
const myDog = new Dog("바둑이");
myDog.speak();
// 출력:
// 바둑이이(가) 소리를 냅니다.
// 바둑이이(가) 멍멍! 짖습니다.
const myCat = new Cat("나비");
myCat.speak();
// 출력:
// 나비이(가) 소리를 냅니다.
// 나비이(가) 야옹~ 소리를 냅니다.
const myCow = new Cow("젖소");
myCow.speak(); // 출력: "젖소이(가) 소리를 냅니다."
메서드 오버라이딩(Overriding): 자식 클래스에서 부모 클래스의 메서드를 재정의하여, 자식 클래스의 특정한 동작을 구현할 수 있습니다.
다형성(Polymorphism): 상속을 통해 객체는 부모 클래스 타입으로도 사용할 수 있으며, 동적 메서드 호출 시 자식 클래스의 메서드가 우선됩니다. 예를 들어, Animal 타입으로 Dog와 Cat을 관리하면서도 각자의 speak() 메서드를 호출할 수 있습니다.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}이(가) 소리를 냅니다.`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name}이(가) 멍멍! 짖습니다.`);
}
}
class Cat extends Animal {
speak() {
console.log(`${this.name}이(가) 야옹~ 소리를 냅니다.`);
}
}
class Cow extends Animal {
speak() {
console.log(`${this.name}이(가) 음매~ 소리를 냅니다.`);
}
}
다형성 활용 예시
const animals = [
new Dog("바둑이"),
new Cat("나비"),
new Cow("젖소")
];
animals.forEach(animal => animal.speak());
바둑이이(가) 멍멍! 짖습니다.
나비이(가) 야옹~ 소리를 냅니다.
젖소이(가) 음매~ 소리를 냅니다.
같은 메서드 호출로 다양한 객체가 각각 다른 동작을 수행하도록 해주는 유연성을 제공하며, 코드의 확장성을 높이고 객체 간의 상호작용을 효율적으로 관리할 수 있게 도와줍니다
오버라이딩
오버로딩
오버로딩 예시 자바 코드
public class Calculator {
// 두 개의 정수를 더하는 메서드
public int add(int a, int b) {
return a + b;
}
// 세 개의 정수를 더하는 메서드
public int add(int a, int b, int c) {
return a + b + c;
}
}
Calculator calculator = new Calculator();
System.out.println(calculator.add(3, 5)); // 출력: 8
System.out.println(calculator.add(3, 5, 2)); // 출력: 10
자바스크립트에서는 오버로딩을 허용하지 않는다
• 모듈화와 팀 협업: 클래스 단위로 모듈화가 가능하기 때문에, 업무 분담이 편리하고 대규모 소프트웨어 개발에 특히 적합합니다. 각 모듈이 독립적으로 작동할 수 있어 충돌을 최소화할 수 있습니다.
• 유지보수 용이: 클래스 단위로 수정이 가능해 특정 기능만 변경할 때에도 다른 부분에 영향을 주지 않으므로 유지보수가 편리합니다.
• 코드 재사용성: 기존 클래스를 재사용하거나 상속을 통해 새로운 기능을 쉽게 추가할 수 있어 코드 재사용이 용이합니다. 이는 개발 시간과 비용을 절감하는 데도 도움이 됩니다.
• 성능 부담: 처리 속도가 상대적으로 느리며, 객체의 수가 많아지면 메모리 사용량이 증가해 프로그램 용량이 커질 수 있습니다.
• 설계 복잡성: 설계 단계에서 객체와 클래스의 관계를 명확히 정의해야 하므로, 많은 시간과 노력이 필요할 수 있습니다. 잘못된 설계는 유지보수성을 떨어뜨릴 수 있어 신중한 설계가 필요합니다.
| 비교 항목 | 객체지향 프로그래밍 (OOP) | 절차지향 프로그래밍 (POP) | 함수형 프로그래밍 (FP) |
|---|---|---|---|
| 중심 개념 | 객체 (클래스) | 함수와 명령어의 순서 | 순수 함수, 불변성 |
| 주요 특징 | 캡슐화, 상속, 다형성 | 단계적 실행, 함수 중심 | 순수 함수, 고차 함수, 함수 조합 |
| 장점 | 유지보수와 확장성 용이, 코드 재사용성 | 직관적인 코드 흐름, 학습이 쉬움 | 가독성 높음, 상태 관리 용이, 병렬 처리 유리 |
| 단점 | 설계 복잡, 메모리 사용량 증가 가능 | 코드 중복 발생, 유지보수 어려움 | 학습 난이도 높음, 성능 이슈 가능 |
| 적합한 용도 | 대규모 소프트웨어, 복잡한 시스템 | 간단한 작업, 성능 중시 소규모 시스템 | 데이터 처리, 수학적 연산, 병렬 연산 |
User 클래스 → 사용자 정보 관리 클래스
이메일 보내는 기능이 User 클래스에 있을경우 이는 SRP를 위반하는 것이다
EmailService 클래스를 만들어, User 클래스는 사용자 정보 관리에만 집중하도록 분리
결제 시스템을 새로운 결제 수단을 추가할때 기존 결제 클래스들을 수정하지 않고 새로운 결제 클래스만 추가하는 방식
만약 Bird 클래스를 상속받은 Penguin 클래스가 있다면, fly() 메서드가 없는 Penguin은 LSP를 위반하는 셈입니다. Bird 클래스에서 fly() 메서드를 제거하거나 인터페이스를 나눠야 할 필요가 있습니다.
Workable 인터페이스에 eat()와 work() 메서드가 모두 포함된 경우, eat() 메서드가 불필요한 Robot 클래스는 ISP를 위반하게 됩니다. 대신 Eatable과 Workable 인터페이스로 나누어, 각 클래스가 필요한 기능만 구현하게 합니다.
자동차 클래스가 구체적인 GasEngine 클래스에 의존하지 않고 Engine 인터페이스에 의존하도록 설계하여, ElectricEngine과 같은 다른 엔진도 쉽게 교체할 수 있게 만듭니다.