본문 바로가기

프로그래밍/기본기ㆍ자료구조

OOP 객체지향 4가지 특징

728x90
반응형

프로그래머의 실력을 판가름할 때 가장 기본적인 부분이다.

알고리즘 문제를 아무리 잘 풀어도, 게임 지식이 아무리 많아도

객체지향 4가지 특징과 5대 원칙에 대한 이해가 없다면 그 사람의 코드는 스파게티코드일 확률이 매우 높다.

그런 사람과의 협업은 협업자들을 굉장히 피곤하게 만든다.

(얽히고 설키고.. 학생 클래스가 학생이면서 책이기도한 괴랄한 구조라던지..)

 

# 추상화

- 추상화 라고 하니까 무슨 말인지 잘 모르는 사람들이 많다.

간단하게 생각하면 abstract, virtual 등을 잘 활용하라는 것이다.

예를들어 아래 코드블럭을 보자

public abstract class Animal
{
    // Animal class를 상속받은 모든 class들이
    // Move()라는 추상적인 함수의 구현을 해야함
    // Animal에서는 직접 구현하지 않고 선언만 해준다.
    public abstract void Move();
}

public class Monkey : Animal
{
    public override void Move()
    {        
        // 두발로 걷는 동작을 구현하면 된다.
    }
}

public class Dog : Animal
{
    public override void Move()
    {        
        // 네발로 걷는 동작을 구현하면 된다.
    }
}

public class Program
{
    public void Main()
    {
        Move(new Monkey());
        Move(new Dog());
    }
    
    public void Move(Animal animal)
    {
        // animal이 monkey인 경우 두발로 걷는다.
        // animal이 dog인 경우 네발로 걷는다.
        animal.Move();
    }
}

Animal이라는 추상적인 Class에 Move()라는 추상적인 함수를 선언만 해놨다.

Animal Class만 봐서는 이 Move()라는 함수가 뭘 하는지는 알 수 없다.

그리고 실제 Move()함수의 구현은 Animal Class를 상속받는 Monkey, Dog Class에서 하게 된다.

이 때 Monkey, Dog라는 객체들을 Animal이라고 묶는 것을 추상화라고 한다.

 

즉, 공통의 속성이나 기능을 묶어 이름을 붙이는 것이라고 할 수 있다.

abstract외에도 상황에 따라 interface나 virtual등을 사용하여도 된다.

개인적으로는 아래 두가지 장점 때문에 추상화를 더욱 신경쓰고 있다.

 

1. abstract나 interface로 추상화를 함으로써 Animal이라는 틀을 강제해 규격화 할 수 있다.

(Animal을 상속받는 Class는 반드시 Animal의 Move()함수의 구현을 해야한다. 이게 왜 중요하냐면 회사에서 타인과 작업을 하게 되면 멋대로 구조를 바꿔버리는 경우가 많다. 그렇게 그 구조는 스파게티가 되기도 하는데 이를 방지하기 위해 강제로 규격화 해버리는 것이다.)

 

2. 아래 코드블럭처럼 각각의 객체로써 구현을 하면 위 코드블럭처럼의 간편함이 사라진다.

(즉, Monkey.Move, Dog.Move . . . . 객체가 늘어날 때마다 Move함수를 불편하게 호출해 주어야 한다는 것이다.

위 코드블럭처럼 그냥 Monkey나 Dog라는 객체를 함수에 넘겨주고 animal.Move를 호출하면 코드도 줄어들고 가독성도 좋아진다.)

public class Monkey
{
    public void Move()
    {        
        // 두발로 걷는 동작을 구현하면 된다.
    }
}

public class Dog
{
    public void Move()
    {        
        // 네발로 걷는 동작을 구현하면 된다.
    }
}

public class Program
{
    public void Main()
    {
        Monkey monkey = new Monkey();
        monkey.Move();
        
        Dog dog = new Dog();
        dog.Move();
        
        Chipmunk chipmunk = new Chipmunk();
        chipmunk.Move();
        .
        .
        .
    }
}

 

# 캡슐화

- 캡슐화는 서로 연관된 함수, 변수 등을 Class나 Sturct로 묶는 것을 말한다.

그리고 외부에서의 접근을 막기 위해 '은닉성'이라는 특징을 갖는다.

public class Chipmunk
{
    public string Name { get; private set; }
    private int age;
    
    public void SetName(string name)
    {
        Name = name;
    }    
    public int GetAge()
    {        
        return age;
    }
    public void SetAge(int age)
    {
        this.age = age;
    }
}

위 코드블럭처럼 Chipmunk라는 Class에 Name, age라는 변수를 묶었다.

그리고 은닉성에 의해 외부에서 직접 수정하지 못 하도록 막아놨다.

Name의 경우 프로퍼티로 구현하여 내부에서만 수정이 가능하도록 private set;을 해주었다.

age의 경우 내부에서만 수정이 가능하도록 private int로 구현하였고 GetAge()함수를 통해서만 얻을 수 있다.

위와 같이 서로 연관된 함수, 변수 등을 Class나 Struct로 묶는 것을 캡슐화라고 한다.

이 캡슐화는 개인적으로 추상화보다 더 중요하게 생각하고 있다.

이전 프로젝트에서 MyInfo라는 슈퍼 슈퍼 Class가 있었는데 끔찍하게도 여기 내용물 대부분이 외부에서 마음대로 참조하여 수정할 수 있게 되어 있었다. 이 MyInfo는 빠르고 편하게 개발하려는 욕심(?) 때문에 아무렇게나 객체를 만들고 참조하고 수정하는 코드들이 넘쳐 스파게티 코드가 되어 있었다.

뭐 하나 수정하려고 하면 엮기고 설킨 녀석들을 일일이 다 찾아서 수정을 해줘야하는 끔찍한 고통을 느껴야 했다.

(캡슐화를 하면 age가 어떤 상황에서 수정되는지 SetAge함수만 로그를 찍던 디버깅하면 되는데 캡슐화가 아니면 age를 직접 참조해서

수정하려고 드는 녀석들을 다 찾아야 한다.)

 

# 상속성

- 공통된 특징을 가진 Class들을 공통된 특징이 구현된 Class를 상속 받음으로써 중복 코드를 방지하고 간편하게 구현할 수 있도록 하는 것이 상속성이다.

아래 코드블럭처럼 Animal이라는 Class에 Breathe라는 공통된 특징을 구현하고

Animal을 상속받는 Monkey나 Dog는 각각 구현할 필요 없이 base.Breathe()를 호출하기만 하면 된다.

public class Animal
{
    public virtual void Breathe()
    {
        // 숨을 쉰다.
    }
}

public class Monkey : Animal
{
    public override void Breathe()
    {
        // 상속 받은 Animal Class의 Breathe() 함수만 호출하면 숨쉬기 구현 끝
        base.Breathe();
    }
}

public class Dog : Animal
{
    public override void Breathe()
    {
        // 상속 받은 Animal Class의 Breathe() 함수만 호출하면 숨쉬기 구현 끝
        base.Breathe();
    }
}

 

# 다형성

- 다형성은 추상화나 상속성과 관련이 있는데 간단히 말해서는 오버라이딩, 오버로딩을 사용한 구현을 말한다.

아래 코드블럭을 보면 Move함수에 Monkey나 Dog를 대입해줬다.

그리고 Move함수에서는 animal.Move를 호출하면 넘겨받은 파라미터가 Monkey 타입이냐 Dog 타입이냐에 따라

다르게 동작하게 된다.

즉, 하나의 클래스나 메소드가 다양한 방식으로 동작이 가능한 것을 의미한다.

 

 

public abstract class Animal
{
    public abstract void Move();
}

public class Monkey : Animal
{
    public override void Move()
    {        
    }
}

public class Dog : Animal
{
    public override void Move()
    {        
    }
}

public class Program
{
    public void Main()
    {
        // Animal을 상속받은 Monkey나 Dog를 Move함수의 파라미터로 넘겨주면
        // Move함수 내부에서는 animal.Move만 호출하면 된다.
        Move(new Monkey());
        Move(new Dog());
    }
    
    public void Move(Animal animal)
    {
        // animal이 monkey인 경우 두발로 걷는다.
        // animal이 dog인 경우 네발로 걷는다.
        animal.Move();
    }
}

 

또한 아래처럼 똑같은 이름인 Move라는 함수 두개를 만들어놓고 넘겨받는 파라미터만 다르게 구현한 것이 있다.

파라미터를 무엇을 넘기냐에 따라 첫번째 Move 함수가 호출될 수도 있고 두번째 Move 함수가 호출될 수도 있다.

public class Program
{
    public void Main()
    {
        Move(new Monkey());
        Move();
    }
    
    public void Move(Animal animal)
    {
        // 여긴 동작 1번
    }
    public void Move()
    {
        // 여긴 동작 2번
    }
}

 

 

어려워보이지만 알고나면 별거 아닌 내용이며 별거 아니지만 굉장히 중요한 내용이기도 하다.

객체지향에 있어서 이 네가지 특징은 반드시 숙지하고 구현하도록 노력해야 한다.

 

다람쥐와 포동포동이

 

 

 

 

 

RememberCook 9월 28일 정식 출시!

두번째 게임인 RememberCook이 출시되었습니다. 귀여운 캐릭터들이 나오는 간단한 게임이며 플레이어의 공간인지능력을 테스트하는 게임입니다. 아래 링크를 통해 다운 받으실 수 있으니 많은 관

chipmunk-plump-plump.tistory.com

반응형