본문 바로가기

객체지향

객체지향이란?

시작하기에 앞서,

객체 지향 프로그래밍(Obejct Oriented Programming)이란, 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다.

그렇다면 자세히 객체지향의 핵심은 무엇일까?

객체지향 핵심

객체지향의 핵심이라 할 수 있는 4가지를 설명하자면 다음과 같다.

  • 추상화
  • 캡슐화
  • 상속
  • 다형성

다음에 대해서 자세히 알아보자.

다음에 대해 설명하기 위해 간단한 예시를 통해 진행하려고 한다.

우리는 어떤 게임을 즐기려고 한다.

해당 게임에서는 다양한 직업을 선택하여 플레이할 수 있다.

우리는 일정 능력치를 가지게 되고 몬스터와 싸우기 위해 계속적으로 성장시켜야한다.

우리는 또한 몬스터와 싸우기 위해 공격을 할 수 있다.

우리는 초반부에 직업을 선택하고 추후에 또 한 번의 전직을 통해 더욱 강해질 수 있다.

이렇게 플레이 하는 게임의 과정을 객체지향 프로그래밍 관점에서 바라보자.

(1) 추상화

'추상화'란, 어떤 실체로부터 공통적인 부분이나 관심 있는 특성들을 한 곳에 모은 것을 의미한다.

즉, 공통의 속성이나 기능을 묶어 이름을 붙이는 것을 의미한다. (클래스를 정의하는 것으로 바라볼 수 있을 것이다.)

우리가 하는 게임 캐릭터들은 다양한 직업을 가지고 있다.

전사, 마법사 등 여러가지 직업들이 존재하고 세부적으로는 두손검을 쓰는 전사, 한손검을 쓰는 전사 등 다양하게 직업군들이 나뉘는 것을 알 수 있다. 하지만 결국 보면 이 모든 직업들은 게임 캐릭터 일환이라고 볼 수 있다.

그래서 이러한 전사, 마법사 등의 다양한 직업군들을 게임캐릭터라는 하나의 직업군으로 추상화해볼 수 있다.

또한, 이 직업군들은 몬스터와 싸우기 위해 능력치를 부여 받는데 체력(HP), 마나(MP), 힘(STR), 민첩(DEX), 마력(INT), 운(LUCK)이라는 공통적인 능력치를 부여받게 되며 공격을 하는 기능을 가진다.

이처럼 게임 캐릭터에 대해 공통의 속성들(능력치), 기능들(공격)을 추상화된 객체인 게임캐릭터에 속성과 기능으로 관리하게 된다.

이를 그림으로 표현하면 다음과 같다.

[추상화]

(2) 캡슐화

'캡슐화'란, 데이터 구조와 데이터를 다루는 방법들을 결합 시켜 묶는 것이다.

특정 객체가 독립적으로 역할을 하기 위해 필요한 데이터와 기능을 묶는 것이다.

우리는 이를 통해 정보은닉을 보장 받을 수 있게 된다.

어떻게 정보 은닉을 제공받을 수 있다는 것일까?

쉽게 예를 들어보자.

우리는 추상화를 통해 게임 캐릭터라는 큰 틀을 잡게 되었다.

이 게임 캐릭터 안에 다양한 능력치들이 부여되어 있는데 누군가 이러한 능력치들에 함부로 접근해서 변경하고 제어할 수 있으면 될까?

당연히 안될 것이다. 그에 따라 이러한 능력치들을 처리할 수 있는 메소드들을 만들어 안전하게 처리하게끔 만드는 것이다.

이처럼 데이터(능력치), 기능(능력치 보여주기 등)을 묶어서 처리하게 된다.

이를 그림으로 표현하면 다음과 같다.

우리는 우리의 능력치를 확인하기 위해 Hp, Mp 등과 같은 능력치에 "직접 접근"하는 것이 아니라 showAbility()라는 메소드를 통해 접근하여 능력치를 확인하는 것을 확인할 수 있다.

[캡슐화]

(3) 상속

'상속'이란, 상위 객체의 특징을 하위 객체가 물려받는 것을 의미한다.

우리는 게임에서 게임 캐릭터를 생성하게 되면 능력치들을 부여받게 된다.

우리는 직업을 선택하고 추후에 추가 전직을 진행하면서 이러한 능력치들을 온전히 가지면서 진행을 하게 될 것이다.

위에서 살펴봤던 것처럼 게임 캐릭터는 모든 직업을 아우르는 상위 객체의 개념이라고 볼 수 있다.

그리고 직업을 선택하고 전직을 하면서 발생된 캐릭터는 하위의 객체 개념이라고 볼 수 있게 될 것이다.

그에 따라 모든 캐릭터들은 모든 능력치들을 그대로 물려받고 사용하는 것이다.

이를 그림으로 표현하면 다음과 같다.

[상속]

(4) 다형성

상위 클래스에서 물려받은 함수를 하위 클래스 내에서 오버라이딩하여 재사용할 수 있는 것이다.

나는 게임에서 전사를 선택하였다고 하고 토모라는 친구는 게임에서 마법사를 선택하였다고 하자.

우리는 같은 게임 캐릭터이긴 하지만 전사는 검을 쓰고 마법사는 마법을 쓰기에 전사와 마법사의 공격은 당연히 다를 것이다.

그에 따라 공격 방법을 달리 해줘야 하는데, 나는 검을 쓸 수 있는 공격 attack()을 오버라이딩 해주고 토모는 마법을 사용할 수 있도록 attack()을 오버라이딩 해줘야 할 것이다.

그리고 추후에 내가 두손검 전사로 전직을 하게 된다면 전직함에 따라 공격은 더욱 강해져야한다.

그에 따라 attack()이라는 메소드를 다시 재정의하여 공격이 더욱 강화되게 할 수 있을 것이다.

이에 대해서는 코드에서 자세히 알아보겠다.

구현

(1) 게임캐릭터

다음은 게임캐릭터를 구현하는 부분이다.

게임캐릭터는 모두 능력치를 가지고 있으며 기본공격과 능력치를 확인할 수 있는 것을 만들었다.

public class Character {
    // 체력과 마나
    private int Hp;
    private int Mp;

    // 능력치
    private int Str;
    private int Dex;
    private int Int;
    private int Luck;

    public String attack() {
        return "툭! 툭! (주먹)";
    }

    public String showAbility() {
        return "Hp=" + Hp +
                ", Mp=" + Mp +
                ", Str=" + Str +
                ", Dex=" + Dex +
                ", Int=" + Int +
                ", Luck=" + Luck;
    }

    public Character() {
        Hp = 500;
        Mp = 500;
        Str = 4;
        Dex = 4;
        Int = 4;
        Luck = 4;
    }

    public int getHp() {
        return Hp;
    }

    public int getMp() {
        return Mp;
    }

    public int getStr() {
        return Str;
    }

    public int getDex() {
        return Dex;
    }

    public int getInt() {
        return Int;
    }

    public int getLuck() {
        return Luck;
    }

    public void setHp(int hp) {
        Hp = hp;
    }

    public void setMp(int mp) {
        Mp = mp;
    }

    public void setStr(int str) {
        Str = str;
    }

    public void setDex(int dex) {
        Dex = dex;
    }

    public void setInt(int anInt) {
        Int = anInt;
    }

    public void setLuck(int luck) {
        Luck = luck;
    }
}

(2) 마법사

마법사는 게임캐릭터를 상속받았다.

마법사는 마법을 쓰는 캐릭터이므로 당연히 마나와 마력이 필요하다.

그에 따라 직업을 선택하는 즉시 마나와 마력을 증가시켜주었다.(super을 이용해 초기화 후 setter를 통한 값 부여)

그리고 공격을 오버라이드하여 마법 공격을 하도록 하였다.

public class Wizard extends Character {
    public Wizard() {
        super();
        super.setInt(super.getInt()+4);
        super.setMp(super.getMp()+200);
    }

    public String attack() {
        return "펑! 펑! (마법)";
    }
}

다음은 테스트이다.

마법사를 선택한 토모가 공격 후 흡족스러워하며 자신의 능력치를 확인하는 모습을 볼 수 있다.

이처럼 공격이 마법공격으로 잘 바뀌어있는 모습을 통해 다형성이 잘 적용되어 있는 모습을 확인할 수 있다.

또한, 능력치에 직접 접근하여 확인하는 것이 아니라 메소드를 통해서 접근하는 방식 즉, 데이터와 기능을 묶어서 사용하는 방식인 캡슐화를 확인할 수 있다.

Character Tommo = new Wizard();
System.out.println("토모의 공격 : " + Tommo.attack());
System.out.println("토모의 능력치 : " + Tommo.showAbility());

(3) 전사

전사는 게임캐릭터를 상속받았다.

전사는 검을 쓰는 캐릭터이므로 당연히 힘과 체력이 필요하다.

그에 따라 마법사와 마찬가지로 증가시켜주었고 공격도 마찬가지로 검 공격을 하도록 하였다.

public class Warrior extends Character {
    public Warrior() {
        super();
        super.setStr(super.getStr()+4);
        super.setHp(super.getHp()+200);
    }

    public String attack() {
        return "샥! 샥! (검)";
    }

    public String skill() {
        return "찌르기! (검)";
    }
}

다음은 테스트이다.

전사를 선택한 길쌍은 토모와 마찬가지로 공격 후 자신의 공격이 흡족스러워 능력치를 확인하는 모습을 볼 수 있다.

이처럼 공격이 검공격으로 잘 바뀌어있고 능력치도 잘 바뀌어 있는 모습을 볼 수 있다.

public class Main {
    public static void main(String[] args) {
        Character GilSSang = new Warrior();
        System.out.println("길쌍의 공격 : " + GilSSang.attack());
        System.out.println("길쌍의 능력치 : " + GilSSang.showAbility());
    }
}

(4) 두손검전사

두손검전사는 전사를 상속받았다.

다음은 연속 상속을 받은 경우인데, "게임캐릭터 - 전사 - 두손검" 다음과 같은 형태로 상속 구조가 이루어져 있고 다음 같은 경우에도 잘 적용이 되는지 확인해보자.

2차 전직을 하였으므로 당연히 더욱 힘과 체력이 증가하고 공격을 변경하였다.

public class TwoHandedSwordWarrior extends Warrior{
    public TwoHandedSwordWarrior() {
        super();
        super.setStr(super.getStr()+4);
        super.setHp(super.getHp()+100);
    }

    public String attack() {
        return "슥삭! 슥삭! (두손검)";
    }

    public String skill() {
        return "내려찍기! (두손검)";
    }
}

다음은 테스트이다.

전사에서 두손검 전사로 전직한 길쌍은 자신의 공격을 테스트해보고 너무 강한 나머지 자신의 능력치를 재빨리 확인하는 모습을 보여준다.

다음 결과처럼 문제없이 잘 적용이 되는 모습을 확인할 수 있다.

public class Main {
    public static void main(String[] args) {
        Character GilSSang = new TwoHandedSwordWarrior();
        System.out.println("길쌍의 공격 : " + GilSSang.attack());
        System.out.println("길쌍의 능력치 : " + GilSSang.showAbility());
    }
}

정리

이처럼 객체지향이 무엇인지, 객체지향의 핵심이 무엇인지, 그리고 실제로 구현하는 것까지 해보았다.

그렇다면 이러한 객체지향을 함으로써의 이점은 무엇일까?

  • 코드를 재사용하기 쉽다.
  • 업그레이드가 쉽다.
  • 디버깅이 쉽다.
  • 신뢰성 있는 소프트웨어를 손쉽게 작성할 수 있다.

하지만 그에 따른 단점도 있을텐데 알아보자.

  • 절차지향 프로그래밍에 비해 실행 속도가 느리다.
  • 필요한 메모리양이 증가한다.

이처럼 장점과 단점이 공존하지만 장점의 이점이 확실히 메리트가 있어 많은 곳에서 객체지향적인 프로그래밍을 채용한다.

우리는 이 객체지향적인 프로그래밍을 잘 적용하기 위해서는 원칙을 자세히 알아볼 필요가 있다.

그에 따라 다음에는 "객체지향 SOLID 원칙"에 대해서 알아보려고 한다.

'객체지향' 카테고리의 다른 글

SOLID 원칙  (1) 2021.09.17