본문 바로가기
프로그래밍 기본 개념

OOP 개념

by 뿌비 2024. 8. 26.
728x90

OOP 란? 

  • OOP는 Object Oriented Programming의 줄임말이다. 
  • 객체 지향 프로그래밍(OOP, Object-Oriented Programming)은 모든 데이터를 객체로 취급하며, 이러한 객체 간의 상호작용을 통해 프로그램을 구성하는 프로그래밍 패러다임이다.
  • OOP는 데이터와 그 데이터를 처리하는 메서드를 하나의 단위로 묶어 객체라고 부른다.

OOP 주요 개념 

1. 캡슐화 (Encapsulation)

  • 객체 안의 데이터(속성)를 숨기고, 그 데이터에 접근할 수 있는 메서드만 제공하는 방식이다.
  • 쉽게 말해, 물건을 상자에 넣고 밖에서 직접 상자를 열지 못하게 하고, 버튼을 눌러서 물건을 꺼내는 것과 비슷하다.

왜 캡슐화가 중요한가?

  • 데이터 보호: 객체 내부의 중요한 데이터를 외부에서 직접 수정할 수 없게 보호한다.
    예를 들어, 속도를 나타내는 speed 값을 마음대로 바꾸는 것을 막을 수 있음
  • 안전한 수정: 속성을 수정하려면 setSpeed() 같은 메서드를 사용해야 한다.
    이 메서드를 통해서만 유효한 값이 설정되므로, 잘못된 값을 넣을 위험이 줄어든다.
public class Car {
// speed라는 속성은 private으로 선언되어 있어 외부에서 직접 접근할 수 없다.
// 외부에서 speed를 수정하려면 반드시 setSpeed()라는 메서드를 사용해야 한다.
    private int speed;  // 감춰진 데이터

    // 속도를 가져오는 메서드
    public int getSpeed() {
        return speed;
    }

    // 속도를 설정하는 메서드
    // 속도가 0보다 클 때만 값을 설정하도록 설계되어 있는데 이렇게 하면 음수나 잘못된 값이 들어가는 것을 방지할 수 있다.
    public void setSpeed(int speed) {
        if (speed > 0) {  // 유효한 값일 때만 설정
            this.speed = speed;
        }
    }

    public static void main(String[] args) {
        Car myCar = new Car();
        myCar.setSpeed(100);  // 속도를 설정
        System.out.println(myCar.getSpeed());  // 속도 출력: 100
    }
}

2. 상속 (Inheritance)

  • 부모 클래스가 가진 속성과 기능을 자식 클래스가 물려받아 사용할 수 있게 하는 개념이다.
  • 자식 클래스는 부모 클래스에서 정의된 메서드나 속성을 그대로 사용하거나, 필요할 때 오버라이드(재정의)하여 자신만의 방식으로 바꿀 수 있다.

왜 상속을 사용할까?

  • 코드 재사용: 부모 클래스에서 정의한 기능을 자식 클래스가 재사용할 수 있다.
  • 유지보수 용이: 중복되는 코드를 없애고, 한 곳에서만 수정하면 자식 클래스에도 반영되므로 유지보수가 쉬워진다.
// 부모 클래스 Animal
class Animal {
    public void sound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

// 자식 클래스 Dog
// Dog 클래스는 Animal 클래스를 상속받아 sound() 메서드를 물려받는다.
class Dog extends Animal {
// sound() 메서드를 자신의 방식으로 다시 정의(오버라이드)하여 '강아지가 멍멍!' 소리를 내도록 수정한다.
    @Override
    public void sound() {
        System.out.println("강아지가 멍멍!");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.sound();  // 강아지가 멍멍!
    }
}

3. 다형성 (Polymorphism)

  • 하나의 메서드나 클래스가 여러 형태로 동작할 수 있는 것을 의미한다.
  • 같은 메서드 이름을 가진 함수가 객체에 따라 다르게 행동하는 것.
  • 이 개념은 코드의 유연성과 확장성을 높이는 데 기여한다.

왜 다형성을 사용할까?

  • 유연성: 같은 메서드라도 다양한 객체에 맞게 동작을 다르게 구현할 수 있다.
  • 확장성: 새로운 클래스가 추가되더라도 기존 코드의 수정 없이 쉽게 통합할 수 있다.
// 부모 클래스 Animal
class Animal {
    public void sound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}


// 자식 클래스 Dog
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("강아지가 멍멍!");
    }
}

// 자식 클래스 Cat
class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("고양이가 야옹!");
    }
}

public class Main {
    public static void main(String[] args) {
    // dog과 cat 객체는 Animal 클래스로 선언되었지만, 실제로는 Dog와 Cat 클래스의 인스턴스이다.
	Animal dog = new Dog();  // Animal 타입으로 Dog 객체 생성
     	Animal cat = new Cat();   // Animal 타입으로 Cat 객체 생성

	// 객체의 실제 타입에 따라 각기 다른 동작을 수행한다. 
        dog.sound();  // 강아지가 멍멍! (다형성에 의해 Dog의 sound() 호출)
        cat.sound();  // 고양이가 야옹! (다형성에 의해 Cat의 sound() 호출)
    }
}

4. 추상화 (Abstraction)

  • 복잡한 시스템을 단순화하여, 꼭 필요한 기능만 노출하고 세부적인 구현은 감추는 방법이다.
  • 예를 들어, 동물이 소리를 내는 방법을 모두 다르게 구현하더라도, 외부에서는 '소리를 낸다'는 기능만 알면 된다.
    즉, 구체적인 동작은 숨기고 그저 '무언가를 한다'는 추상적인 개념만 제공한다.

왜 추상화를 사용할까?

  • 복잡성 감소: 불필요한 세부 사항을 숨기고, 중요한 부분만 남겨두어 시스템을 쉽게 이해하고 사용할 수 있다.
  • 유연성: 구현 방식은 나중에 구체적으로 정해도 되므로, 더 다양한 구현이 가능하다.
// Animal 클래스는 추상 클래스이며, sound() 메서드를 자식 클래스가 구현하도록 강제한다.
abstract class Animal {
    // 추상 메서드: 구현은 자식 클래스가 책임짐
    abstract void sound();  
}

// Dog와 Cat 클래스는 sound() 메서드를 각각 자신만의 방식으로 구현한다.
// 자식 클래스 Dog
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("강아지가 멍멍!");
    }
}

// 자식 클래스 Cat
class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("고양이가 야옹!");
    }
}

public class Main {
    public static void main(String[] args) {
    // 추상화를 통해 복잡한 동작을 감추고, 외부에는 단순한 인터페이스(기능)만 제공한다.
        Animal dog = new Dog();
        Animal cat = new Cat();

        dog.sound();  // 강아지가 멍멍!
        cat.sound();  // 고양이가 야옹!
    }
}

5. 종속성 역전 (Dependency Inversion)

  • 상위 모듈(큰 개념)이 하위 모듈(세부적인 구현)에 의존하는 전통적인 방식에서 벗어나, 하위 모듈이 상위 모듈에 의존하도록 구조를 바꾸는 개념이다.
  • 이 방식은 시스템의 유연성과 확장성을 높이는 데 큰 도움을 준다.

왜 종속성 역전을 사용할까?

  • 유연한 설계: 구체적인 구현에 의존하지 않고 추상화된 개념에 의존하므로, 새로운 기능을 쉽게 추가하거나 교체할 수 있다.
  • 유지보수 용이: 코드 수정이 필요할 때 영향을 최소화할 수 있다.
// 엔진에 대한 인터페이스
interface Engine {
    void start();
}

// 구체적인 가솔린 엔진 구현
class GasEngine implements Engine {
    public void start() {
        System.out.println("가솔린 엔진이 시작됩니다.");
    }
}

// 구체적인 전기 엔진 구현
class ElectricEngine implements Engine {
    public void start() {
        System.out.println("전기 엔진이 시작됩니다.");
    }
}

// 자동차 클래스
class Car {
    private Engine engine;  // Engine 인터페이스에 의존

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void startCar() {
        engine.start();  // 엔진에 의존하여 차를 시작
    }

    public static void main(String[] args) {
    // 새로운 엔진 타입이 추가되더라도, Car 클래스는 변경할 필요가 없습니다. 단지 새로운 엔진 구현체만 추가하면 된다.
    // 종속성 역전을 통해 Car는 엔진의 세부 구현을 알 필요 없이, 다양한 엔진을 쉽게 교체할 수 있다.
        Car gasCar = new Car(new GasEngine());  // 가솔린 엔진 사용
        Car electricCar = new Car(new ElectricEngine());  // 전기 엔진 사용

        gasCar.startCar();  // 가솔린 엔진이 시작됩니다.
        electricCar.startCar();  // 전기 엔진이 시작됩니다.
    }
}

※ 요약

  • 캡슐화: 데이터를 보호하고 메서드를 통해서만 접근하게 한다.
  • 상속: 부모가 가진 기능을 자식이 물려받아 사용한다.
  • 다형성: 같은 메서드가 다른 객체에서 다르게 동작한다.
  • 추상화: 복잡한 부분을 숨기고 필요한 기능만 제공한다.
  • 종속성 역전: 하위 모듈(엔진)이 상위 모듈(차)에 의존하도록 설계한다.
728x90