떼닝로그

[CS] 객체 지향 프로그래밍의 특징 본문

개발로그/기타 이론 정리

[CS] 객체 지향 프로그래밍의 특징

떼닝 2024. 7. 5. 18:12

객체 지향 프로그래밍(Object-Oriented Programming, OOP)

- 프로그래밍에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체로 만들고, 객체들간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법

- 여러 독립적인 부품들의 조합, 즉 객체들의 유기적인 협력과 결합으로 파악하고자 하는 컴퓨터 프로그래밍의 패러다임

- 객체 : 클래스의 인스턴스나 배열. 속성과 기능을 가지는 프로그램 단위. 모든 실재하는 대상

- 객체 지향 프로그래밍의 장점 : 보다 유연하고 변경이 용이하게 만들기 가능, 코드의 변경을 최소화하고 유지보수를 하는 데 유리, 코드의 재사용을 통한 반복적인 코드 최소화, 코드를 최대한 간결하게 표현 가능, 인간 친화적이고 직관적인 코드 작성에 용이

 

객체 지향 프로그래밍의 특징

추상화 (Abstraction)

- 추상화 : 객체의 공통적인 속성과 기능을 추출하여 정의하는 것

- 자동차와 오토바이는 모두 Vehicle이라는 특징을 가지며, start, moveForward, moveBackward의 공통적인 기능을 수행할 수 있음

- 아래는 Vehicle Interface

public interface Vehicle{
    public abstract void start();
    void moveForward();	// public abstract 생략 가능
    void moveBackward();
}

 

- 아래는 Car class

public class Car implements Vehicle{ // 이동수단을 구체화한 자동차 클래스

    @Override
    public void moveForward(){
    	System.out.println("자동차 전진");
    }
   
    @Override
    public void moveBackward(){
    	System.out.println("자동차 후진");
    }
}

 

- 아래는 Motorbike class

public class MotorBike implements Vehicle{

    @Override
    public void moveForward(){
    	System.out.println("오토바이 전진");
    }
    
    @Override
    public void moveBackward(){
    	System.out.println("오토바이 후진");
    }
}

 

- 이런 것을 객체 지향 프로그래밍에서는 역할과 구현의 분리라고 함

- 이 부분이 다형성과 함께 유연하고 변경이 용이한 프로그램을 설계하는 데 가장 핵심적인 부분

- 역할에 해당하는 부분이 인터페이스를 통해 추상화될 수 있음

 

상속 (Inheritance)

- 상속 : 기존의 클래스를 재활용하여 새로운 클래스를 작성하는 자바의 문법 요소

- 상위 클래스로부터 확장된 여러 개의 하위 클래스들이 모두 상위 클래스의 속성과 기능들을 간편하게 사용할 수 있도록 함

- 공유하는 속성과 기능들을 한 번만 정의해두고 재사용할 수 있어, 반복적인 코드 최소화하고 공유하는 속성과 기능에 간편하게 접근 가능

- 아래는 Vehicle class

public class Vehicle { //추상화를 통한 상위클래스 정의

    String model;
    String color;
    int wheels;
    
    void moveForward() {
    	System.out.println("전진합니다.");
    }
    
    void moveBackward() {
    	System.out.println("후진합니다.");
    }
}

 

- 아래는 각각 Car, MotorBike class

// Car
public class Car extends Vehicle {

    boolean isConvertible;
    
    void openWindow(){
        System.out.println("모든 창문 열기");
    }
}

// MotorBike
public class MotorBike extends Vehicle {

    boolean isRaceable;
    
    // 메서드 오버라이딩 -> 기능 재정의
    @Override
    void moveForward() {
    	System.out.println("오토바이가 앞으로 전진합니다.");
    }
    
    public void stunt(){
    	System.out.println("오토바이로 묘기를 부립니다.");
    }
}

 

 

- Car와 MotorBike class의 공통적인 속성과 기능들을 추출(추상화)하여 Vehicle 클래스 정의

- extends 키워드를 통해 각각의 하위 클래스로 확장하여 해당 기능과 속성들을 반복적으로 정의해야 하는 번거로움 제거

- 공통적인 코드의 변경이 있는 경우, 상위 클래스에서 단 한 번의 수정으로 모든 클래스에 변경 사항 반영

- 각 클래스의 맥락에 맞게 메서드 오버라이딩(method overriding)을 사용하여 내용 재정의 가능

 

- 인터페이스를 통한 구현과 상속 모두 상위 클래스-하위 클래스의 관계를 전제하면서 공통적인 속성과 기능 공유

- 상속하위 클래스의 속성과 기능들을 하위 클래스에서 그대로 받아 사용하거나 오버라이딩을 통해 선택적으로 재정의하여 사용 가능

- 인터페이스를 통한 구현반드시 인터페이스에 정의된 추상 메서드의 내용이 하위 클래스에서 정의되어야 함

 

- 상속 관계의 경우, 인터페이스를 사용하는 구현에 비해 추상화의 정도가 낮음.

 

다형성 (Polymorphism)

- 다형성 : 어떤 객체의 속성이나 기능이 그 맥락에 따라 다른 역할을 수행할 수 있는 객체 지향의 특성

- 메서드 오버라이딩, 메서드 오버로딩이 다형성의 대표적인 예시

 

 

- 객체 지향 프로그래밍에서 다형성이란, 한 타입의 참조변수를 통해 여러 타입의 객체를 참조할 수 있도록 만든 것.

- 즉, 상위 클래스 타입의 참조변수로 하위 클래스의 객체를 참조할 수 있도록 하는 것.

- 아래는 다형성 개념을 활용하여 객체를 생성하는 방법에 대한 코드

public class Main {
    public static void main(String[] args) {
    	
        // 원래 사용했던 객체 생성 방식
        Car car = new Car();
        MotorBike motorbike = new MotorBike();
        
        // 다형성을 활용한 객체 생성 방식
        Vehicle car2 = new Car();
        
    }
}

 

- 다형성을 사용하면 여러 종류의 객체를 배열로 다루는 일이 가능해짐

public class Main {
	public static void main(String[] args){
    
    	// 상위 클래스 타입의 객체 배열 생성
        Vehicle vehicles[] = new Vehicle[2];
        vehicles[0] = new Car();
        vehicles[1] = new MotorBike();
        
        for (Vehicle vehicle : vehicles) {
        	System.out.println(vehicle.getClass());	// 각각의 클래스를 호출해주는 메서드
        }
    }
}

// 출력값
class Car;
class MotorBike;

 

- 다형성을 활용하면 하나의 타입만으로 여러 가지 타입의 객체를 참조할 수 있어, 보다 간편하고 유연하게 코드 작성 가능

 

- 하나의 객체가 다른 객체의 속성과 기능에 접근하여 어떤 기능을 사용할 때, 이것을 A 클래스는 B 클래스에 의존한다 라고 표현

- 객체들 간의 결합도가 높은 상태는 객체 지향적인 설계를 하는 데 매우 불리

- 객체 지향 프로그래밍은 설계할 때 역할과 구현을 구분하여 객체들 간의 직접적인 결합을 피하고, 느슨한 관계 설정을 통해 보다 유연하고 변경이 용이한 프로그램 설계를 가능하게 만듦

// Vehicle 인터페이스
public interface Vehicle {	// 이동수단의 역할 정의
    void moveForward();
    void moveBackward();
}


// Car 클래스
public class Car implements Vehicle {	// 이동수단 인터페이스를 구현
    @Override
    public void moveForward(){
    	System.out.println("자동차가 앞으로 전진합니다.");
    }
    
    @Override
    public void moveBackward() {
    	System.out.println("자동차가 뒤로 후진합니다.");
    }
}

// MotorBike 클래스
public class MotorBike implements Vehicle {	// 이동수단 인터페이스를 구현
    @Override
    public void moveForward() {
    	System.out.println("오토바이가 앞으로 전진합니다.");
    }
    
    @Override
    public void moveBackward() {
    	System.out.println("오토바이가 뒤로 후진합니다.");
    }
}

// Driver 클래스
public class Driver {
    void drive(Vehicle vehicle) {	// 매개변수로 인터페이스 타입의 참조변수를 전달
    	vehicle.moveForward();
        vehicle.moveBackward();
    }
}


// Main 실행 클래스
public class Main {
    public static void main(String[] args) {
    	Car car = new Car();
        MotorBike motorBike = new MotorBike();
        Driver driver = new Driver();
        
        driver.drive(car);
        driver.drive(motorBike);
    }
}



// 출력값
자동차가 앞으로 전진합니다.
자동차가 뒤로 후진합니다.
오토바이가 앞으로 전진합니다.
오토바이가 뒤로 후진합니다.

 

- Vehicle 인터페이스를 통해 이동 수단의 역할을 추상화하고, 각각 Car 클래스와 MotorBike 클래스에서 기능 구현

- Driver 클래스는 drive 메서드로 전달되는 매개변수의 타입을 상위 클래스인 인터페이스 타입 Vehicle로 설정

- Vehicle 인터페이스를 통해 Driver 클래스와 Car, MotorBike 클래스 간의 결합도가 낮아짐

- Driver 클래스는 더 이상 각각의 클래스 내부의 변경이나 다른 객체가 새롭게 교체되는 것을 신경 쓰지 않아도, 인터페이스에만 의존하여 수정이 있을 때마다 코드 변경을 하지 않아도 됨

 

캡슐화 (Encapsulation)

- 캡슐화 : 클래스 안에 서로 연관 있는 속성과 기능들을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것

- 캡슐화를 하는 대표적인 이유 두 가지 : 데이터 보호, 데이터 은닉

- 데이터 보호 (data protection) : 외부로부터 클래스에 정의된 속성과 기능들을 보호

- 데이터 은닉 (data hiding) : 내부의 동작을 감추고 외부에는 필요한 부분만 노출

 

- 캡슐화의 목적 : 외부로부터 클래스에 정의된 속성과 기능들을 보호하고, 필요한 부분만 외부로 노출될 수 있도록 하여 각 객체 고유의 독립성과 책임 영역을 안전하게 지키고자 함

- 접근 제어자(access modifier)를 클래스 또는 클래스의 내부의 멤버들에 사용하여 해당 클래스나 멤버들을 외부에서 접근하지 못하도록 접근을 제어함

 

- 자바에는 public, default, protected, private로 총 네 가지 종류의 접근 제어자가 존재

 

- 자바의 캡슐화를 구현하기 위해 getter/setter를 사용하여 선택적으로 외부에 접근을 허용할 속성과 그렇지 않을 속성을 설정

- 캡슐화를 활용하여 객체의 자율성, 즉 하나의 객체가 해당 객체의 속성과 기능에 대한 독자적인 책임을 담당하도록 만들고, 이를 통해 객체 간의 결합도를 낮게 유지 가능

 

// Car 클래스
public class Car {
    
    private String model;
    private String color;
    
    public Car(String model, String color) {
    	this.model = model;
        this.color = color;
    }
    
    private void startEngine() {
    	System.out.println("시동을 겁니다.");
    }
    
    private void moveForward() {
    	System.out.println("자동차가 앞으로 전진합니다.");
    }
    
    private void openWindow() {
    	System.out.println("모든 창문을 엽니다.");
    }
    
    public void operate() { // 앞서 driver 클래스에 정의된 메서드들 이동하여 메서드 추출
    	startEngine();
        moveForward();
        openWindow();
    }
}


// Driver 클래스
public class Driver {
    
    private String name;
    private Car car;
    
    public Driver(String name, Car car) {
    	this.name = name;
        this.car = car;
    }
    
    public String getName() {
    	return name;
    }
    
    public void drive() {
    	car.operate();	// Car 클래스에 있는 메서드를 단순하게 호출
    }
}


// Main 실행 클래스
public class Main {
    public static void main(String[] args) {
    	
        Car car = new Car("테슬라 모델X", "레드");
        Driver driver = new Driver("김코딩", car);
        
        driver.drive();
    }
}


// 출력값
시동을 겁니다.
자동차가 앞으로 전진합니다.
모든 창문을 엽니다.

 

- 캡슐화를 활용하면, 객체 내부 동작의 외부로의 노출을 최소화하여 각 객체의 자율성을 높이고, 이를 통해 객체 간 결합도를 낮춰 객체 지향의 핵심적인 이점을 잘 살릴 수 있음

 

 

 

참조)

https://jongminfire.dev/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%B4%EB%9E%80

https://upcake.tistory.com/418

https://www.codestates.com/blog/content/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%ED%8A%B9%EC%A7%95

 

 

 

Comments