객체지향 프로그래밍

현곤·2024년 11월 24일

프로그래밍에는 각 상황에 적합한 정말 다양한 방식이 있다.

그 중에서도 주로 언급되는 두 가지가 바로
'절차 지향 프로그래밍'(Proceddural Programing),
'객체 지향 프로그래밍'(Object-Oriented Programing, OOP )이다.

절차 지향 프로그래밍

절차 지향 프로그래밍은 마치 레시피를 따르는 것과 비슷하다.

예시 )

요리를 할 때

  • 재료 준비
  • 재료 다듬기
  • 조리

절차지향 프로그래밍에서는프로그램을 여러 개의 순서대로 진행되는 절차(함수나 명령)들로 구성한다.

이런 방식은 프로그램이 어떻게 실행되어야 하는지, 단계별로 무엇을 해야 하는지를 중시한다.

예를 들어, "학교에서 집까지 가는 길을 찾는 프로그램"을 만든다고 생각해보자.

절차 지향 방식 접근

  • 학교에서 출발해야 한다는 절차를 정의
  • 길을 따라 가는 방법, 교통 수단을 선택하는 방법

차례로 명령을 만들어 나가면 된다.


객체 지향 프로그래밍

객체 지향 프로그래밍은 좀 더 현실 세계에 가까운 방식으로 프로그래밍하는 것이다.

현실 세게에는 많은 '객체' 들이 있듯이, OOP 에서는 모든 것을 객체로 바라본다.

여기서 객체란 데이터(속성)와 그 데이터와 관련된 기능(메서드)을 하나로 묶은 것을 말한다.

객체 지향 방식을 적용할 때는 프로그램이 해결해야 할 문제를 작은 부분들로 나누고,
각 부분을 '객체'로 만든다.

이 객체들은 서로 '상호작용'하면서 전체 프로그램이 작동하게 된다.

객체들은 각자의 데이터를 가지고 있으며,
다른 객체들과 메시지를 주고 받으며 상호작용할 수 있다.

객체 지향 방식 접근

  • '학교'
  • '집'
  • '길'
  • '교통 수단'

각각의 객체로 정의한다.

이 객체들이 서로 상호작용하면서 집에 도착하는 방법을 찾도록 만든다.


두 방식을 서로 비교해보자.

  • 절차 지향 프로그래밍은 프로그램을 명령의 순서와 절차로 보는 방식
    이 방식은 간단한 프로그램을 빠르게 만들 때 유용하다.

  • 객체 지향 프로그래밍은 객체를 모델링하여 프로그램을 구성하는 방식
    이 방식은 복잡하거나 유지보수와 확장성이 중요한 프로그램을 개발할 때 강점을 보인다.

이 두 방식은 프로그램을 어떻게 바라보고 구성하느냐의 차이다.

절차 지향은 단계를 따라가며 문제를 해결한다.

객체 지향은 객체들이 상호작용하며 문제를 해결하는 방식이다.

프로그래밍을 배울 때는 이 두 가지 방식 모두를 이해하고,
상황에 맞게 적절히 선택해서 사용하는 것이 중요하다.

예제 )
"은행 계좌 관리 시스템"
계좌의 잔액을 확인하고, 입금과 출금 기능을 수행할 수 있어야 한다.
1. 절차 지향 방식으로 구현 후
2. 객체 지향 방식으로 구현


예제로 보는 절차 지향

절차 지향적 코디에서는 함수를 중심으로 코드를 작성한다.

여기서는 계좌의 잔액을 전역 변수로 관리, 입금과 출금을 위한 함수를 정의한다.

public class BackAccountProcedural {

    // 전역 변수로 계좌 잔액 선언
    public static int accountBalance = 0;

    // 잔액 확인 함수
    public static void 잔액() {
        System.out.println("현재 잔액은 " + accountBalance + "원 입니다.");
    }

    // 입금 함수
    public static void 입금 (int won) {
        accountBalance += won;
        System.out.println(won + "원 입금했습니다.");
    }

    // 출금 함수
    public static void 출금 (int won) {
        if (accountBalance >= won) {
            accountBalance -= won;
            System.out.println(won + "원 출금했습니다.");

        } else {
            System.out.println("잔액이 부족합니다.");
        }
    }
}
        // 메인 메소드에서 함수 사용 예시
public class Main {
    public static void main(String[] args) {
        BackAccountProcedural 은행 = new BackAccountProcedural();

        BackAccountProcedural.잔액();
        BackAccountProcedural.입금(100000);
        BackAccountProcedural.잔액();
        BackAccountProcedural.출금(50000);
        BackAccountProcedural.잔액();
        
    }
}
// 출력

현재 잔액은 0원 입니다.
100000원 입금했습니다.
현재 잔액은 100000원 입니다.
50000원 출금했습니다.
현재 잔액은 50000원 입니다.

예제로 보는 객체 지향

객체지향적 코딩에서는 계좌를 하나의 '객체'로 취급한다.
이 객체는 자신의 잔액을 속성으로 가지고 입금, 출금 같은 기능을 메서드(함수)로 가진다.

public class Main {
    public static void main(String[] args) {

        Money money = new Money();

        money.내돈();
        money.입금(100000);
        money.내돈();
        money.출금(50000);
        money.내돈();

    }
}

class Money {
    private int balance = 0;

    public int 잔액() {
        return balance;
    }

    public void 내돈() {
        System.out.println("현재 잔액은 " + 잔액() + " 원 입니다.");
    }

    public void 입금 (int won) {
        balance += won;
        System.out.println(won + "원을 입금했습니다.");
    }

    public void 출금 (int won) {
        if (잔액() >= won) {
            balance -= won;
            System.out.println(won + "원을 출금했습니다.");

        } else {
            System.out.println("잔액이 부족합니다.");
        }
    }
}

절차 지향적 코드는 기능 중심으로 구성되어 있고, 전역 변수를 통해 상태를 관리한다.

반면, 객체 지향적 코드는 상태와 행위를 하나의 클래스 라는 단위로 묶어 관리한다.
클래스 내부를 수정하면 해당 클래스를 이어받는 여러 객체들과 변경사항을 공유할 수 있다.

이러한 면을 통해 코드의 재사용성과 유지보수성이 상승한다.

객체 지향적 코딩이 특히 유용한 상황 중 하나는 여러 개체가 서로 다른 행동을 해야할 때다.

  • 은행 시스템을 확장하여 다양한 종류의 계좌 ( 예시: 저축 계좌, 체크 계좌 )
    OOP 는 이런 상황에서 코드의 재사용성과 확장성을 크게 향상

  • 계좌 종류에 다른 규칙 ( 예시 : 이자율, 수수료 등 )
    객체 지향 접근 방식 을 사용하면 '계좌'라는 기본 클래스를 정의하고,
    이를 상속받아 각 계좌 종류에 맞는 특성을 갖는 서브 클래스를 만들 수 있다.


절차 지향적 코드 심화 예제

BackAccount class (부모)


public class BackAccount {
    
    // 캡슐화
    private String owner;
    private double balance;

    public BackAccount(String owner, double balance) {
        this.owner = owner;
        this.balance = balance;
    }

    public BackAccount(String owner) {
        this(owner, 0);
    }

    public void 입금(double won) {
        this.balance += won;
        System.out.println("%s + 님, %.0f원을 입금하셨습니다.".formatted(owner, balance));
    }

    public void 출금(double won) {
        if (this.balance >= won) {
            this.balance -= won;
            System.out.println("%s + 님, %f원을 출금하셨습니다. 현재 잔액은 %.0f원입니다.%n".formatted(owner, won, balance));
        } else {
            System.out.println("잔액이 부족합니다.");
        }
    }
    
    // get 창구
    public String getOwner() {
        return owner;
    }
    public double getBalance() {
        return balance;
    }

    // set 창구
    public void setBalance(double balance) {
        this.balance = balance;
    }
    
}

SavingAccount class (자식)

// 상속
public class SavingAccount extends BackAccount {
    private double interestRate;

    public SavingAccount (String owner,double balance ,double interestRate) {
        super(owner, balance);
        this.interestRate = interestRate;
    }

    public SavingAccount (String owner, double balance) {
        this(owner, balance, 0.02);     // 기본 이자율 2%
    }

    public void applyInterest() {
        double interest = getBalance() * interestRate;
        setBalance(getBalance() + interest);
        System.out.printf("%s님의 계좌에 이자 %.0f원이 지급되었습니다. 현재 잔액은 %.0f원 입니다.%n", getOwner(), interest, getBalance());
    }
}

CheckingAccount class (자식)

// 상속
public class CheckingAccount extends BackAccount {
    private double transactionFee;

    public CheckingAccount(String owner, double balance, double transactionFee) {
        super(owner, balance);
        this.transactionFee = transactionFee;
    }

    public CheckingAccount(String owner, double balance) {
        this(owner, balance, 100);      // 기본 수수료 100원
    }

    // 생성자 오버로딩
    @Override
    public void 출금(double won) {
        double totalwon = won + transactionFee;
        if (getBalance() >= totalwon) {
            setBalance(getBalance() - totalwon);
            System.out.printf("%s님의 %.0f원을 출금하셨습니다. 수수료 %.0f원이 부과되었습니다. 현재 잔액은 %.0f원입니다.%n", getOwner(), won, transactionFee, getBalance());
        } else {
            System.out.println("잔액이 부족합니다.");
        }
    }
}

이 예제에서는 BackAccount라는 기본 클래스를 만들고
상속받는 SavingAccount, CheckAccount 클래스를 정의했다.

각 클래스는 상속을 통해 BackAccount의 기능을 재사용하면서,
각 계좌 유형에 특화된 기능(이자 적용, 거래 수수료 부과)을 추가로 구현할 수 있다.

객체는 서로 구분되어 같은 메소드를 활용하더라도 서로 다른 상태를 유지할 수 있다.


객체 지향 프로그래밍은 코드의 재사용성과 유지 보수성을 높이고, 시스템의 확장성을 개선하는데 유리하다.
특히 여러 유형의 객체가 서로 다른 행동을 해야 하는 복잡한 시스템을 개발할 때 사용한다.

profile
코딩하는 곤쪽이

0개의 댓글