객체 지향 프로그래밍 OOP
구조적 프로그래밍으로 소프트웨어 유지보수와 재사용이 어렵다는 문제 해결을 위해 도입된 프로그래밍 방법
JAVA, Python, C++, C#, Ruby, Swift 등 여러 언어에서 객체 지향 프로그래밍 방법을 사용한다.
객체 지향 프로그래밍의 주 특징 4가지는 상속, 다형성, 추상화, 캡슐화가 있다.
주요 특징 4개 뿐만 아니라 객체 지향에서는 여러 특징을 만날 수 있다. 의존성 문제나 응집도, 결합도, 객체 지향 설계 5대 원칙 SOLID 등 그 개념은 아주 많으며 객체 지향 프로그래밍을 사용하는 사람들은 효율적으로 설계하기 위해 노력해야 한다.
관련 용어
1. 협력 collaboration
객체가 기능을 구현하기 위해 수행하는 상호 작용
2. 책임 responsibility
협력에 참여하기 위해 수행하는 로직
3. 역할 role
협력에서 수행하는 책임이 모여 객체가 수행
먼저 객체가 무엇인지, 어떻게 객체가 생성이 되는지 알아보자.
추상 자료형 Abstract Data Type:ADT
추상(Abstract)이라는 말은 객체 지향에서 아주 중요하게 쓰이는 용어이다. 객체 지향 주요 특징 4개 중 하나가 추상화일 정도로 객체 지향에서는 '추상적이다' 라는 개념에 주목해야 한다.
ADT는 사용할 데이터를 정의하고 그 데이터에 필요한 함수를 정의하는 것이다. 데이터 타입에 연관된 모든 데이터+연관된 함수를 하나의 모듈이라고 한다. 이 모듈을 구현할 때 그 방법은 명시하지 않고, 추상적으로 정의되는 것이 추상 자료형이다. 이 추상자료형은 객체로 전달이 된다.
객체 Obejct
내부 정보가 다른 객체와 대화하게 하는 하나의 집합체로 볼 수 있다. 다른 객체에게 '메서드'로 '작업'을 요청할 수 있냐고 접근하는 것은 메시지 전달이라고 한다. 메시지를 전달하면서 서로 다른 객체끼리 상호작용을 한다.
내 객체와 다른 객체가 같이 상호작용을 하지만, 나는 다른 객체의 데이터를 변경시킬 수 없다. 그 이유는 객체가 어떤 것을 가지고 있는지, 어떻게 생성이 되는지 알면 이해할 수 있다.
클래스 Class
객체를 생성하는 형판을 클래스라고 한다. 보통 클래스를 설계도라고 비유하는데, 클래스라는 설계도를 실제로 구현한 것이 객체이다. 클래스는 하나의 타입으로 볼 수 있고, 이 타입(해당 클래스)을 가지는 객체는 하나의 클래스(해당 클래스)를 표현한다. 같은 클래스를 가지고 있는 객체라도 서로 Unique한 것이 특징이다.
각 객체는 인스턴스 될 때, 메모리를 할당받는다. 각 객체마다 고유한 주소가 존재하는 것이다. 메모리 상에서 서로 다른 공간에 위치해 그저 같은 클래스를 표현하는 것일 뿐, 두 객체는 서로 내부에 접근할 수 없는 다른 존재다.
데이터와 관련된 메서드를 추상 자료형으로 구현한 것이 이 클래스로 볼 수 있다. 객체는 클래스의 인스턴스(실체)로 설계도를 제공하는 클래스는 정적이고, 그 설계도에 따라 실제로 구현한 객체는 동적이다.
객체 지향 프로그래밍의 4가지 특징
1. 상속
한 클래스가 다른 클래스를 물려받는 것을 상속이라고 한다. 일상에서 상속을 받는다는 말을 할 때, 부모의 재산을 자식이 물려받는다는 의미로 사용한다. 객체 지향에서도 그 의미가 그대로 적용된다. 다른 클래스를 상속하는 클래스는 자식클래스라고도 부르며, 자식 클래스에게 상속을 해주는 클래스를 부모 클래스라고 한다. 그리고 클래스에서 보호해야 하는 존재인 데이터와 그 데이터를 사용하기 위한 메서드, 즉 부모클래스의 재산을 자식클래스가 그대로 물려받는다.
상속은 소프트웨어의 원소인 객체의 재사용을 목적으로 한다. 그저 같은 코드를 재사용하는 것이 아닌 클래스 자체를 재사용하는 것이 목적이다. 부모 클래스는 상위 클래스, 자식 클래스는 하위 클래스라고도 부르며, 클래스 다이어그램을 작성할 때 하위 클래스가 상위 클래스 아래에 위치해 화살표로 가리키고 있다.
- 다중 상속
"자바는 다중 상속을 허용하지 않는다." 다중상속이란 2개 이상의 상위클래스를 상속받는 것을 의미한다. 하나의 상위클래스에 하위클래스가 여러개인 것이 아니라, 여러 개의 상위클래스에 하나의 하위클래스 형태이다.
자바는 왜 다중상속을 허용하지 않을까? 다른 클래스를 상속하면 자식은 부모의 재산을 모두 물려받는다. 클래스의 재사용을 위해 사용하는 상속이니, 여러 클래스를 한번에 물려받으면 더 좋은 효과가 나지 않을까? 그 이유를 알아보자.
- 변수와 메서드의 모호함
- A, B 두개의 상위클래스에 같은 이름을 가진 속성과 메서드가 존재한다고 가정했을 때, 이 두개를 모두 상속받는 C클래스에서 이 메서드를 재사용하기 위해 호출한다.
- C++에서는 다중 상속 시 어떤 부모에게서 호출하는지 구분하기 위해 닷연산을 사용하지만, 자바는 호출하는 메서드의 클래스를 명시하지 않는다. 그저 메서드의 이름만 가져와 오버라이딩한다.
- C클래스는 메서드를 호출하려고 했지만, 컴퓨터는 A,B 중 어떤 클래스에서 가져온 메서드인지 알 수 없다.
- 기능의 무거움
- 자식은 부모의 속성과 메서드를 전부 받아온다. n개의 클래스를 상속받는 자식클래스 X가 있다. 이 X클래스는 n개의 부모클래스의 모든 속성과 메서드를 사용할 수 있다.
- 작은 프로젝트 일 때 그 차이는 크지 않지만, 매우 큰 프로젝트를 제작할 때 컴퓨터 자체에 부담이 된다.
- 리스코프 치환 원칙 Liskov Substitution Principle:LSP
객체 지향 5대 원칙 중 하나인 리스코프 치환 원칙은 상속에 대한 내용이 적혀있다.
자식 클래스는 부모 클래스를 대체할 수 있는 경우에만 사용한다.
2. 다형성
여러 타입을 대상으로 동작할 수 있는 것을 다형성이라고 한다. 하나의 타입이 아닌 여러 타입이 올 수 있다고 그 범위를 넓히는 것. 어떤 타입이 와도 기능이 제대로 동작하게 만드는 것이 중요하다. 다형성은 주로 추상 인터페이스 생성 후, 서로 다른 구현을 연결한다. 일반적으로 상속을 통해 구현되어 다형성과 상속은 긴밀한 관계를 맺고 있다.
1) 오버로딩 다형성
하나의 클래스에 동일한 이름의 메서드, 즉 오버로딩(Overloading)된 메서드를 오버로딩 다형성이 이루어진다고 한다.
메서드 오버로딩은 같은 이름을 가진 여러개의 메서드가 다른 타입의 매개변수, 다른 개수의 매개변수를 가지는 경우 생성할 수 있다.
예를 들어 더하기는 진행하는 add(int x,int y)
메서드가 있을 때 매개변수로 x와 y를 받고, 이 두 매개변수는 int타입이다.
정수가 아닌 정수와 실수, 혹은 실수와 실수를 입력받고 싶을 때는 그 매개변수 타입을 변경하여 새로 생성할 수 있다.
add(float x, float y)
와 add(int x, float y)
를 생성했다. 세개 모두 add라는 같은 이름을 가지고 있지만, 다른 기능을 하는 다른 메서드이다. add(int x, int y, int z)
는 생성할 수 있을까? 이것 역시 생성할 수 있다. 기존의 add(int, int)
와 사용하는 데이터 타입은 같지만 매개변수의 수가 다르다. 이처럼 메서드 오버로딩을 하면 같은 add메서드를 이용해서 여러 타입 연산이 가능해졌다. 다형성을 구현한 것이다.
2) 강제 다형성
타입 변환을 통해 동일한 연산자를 다양한 타입에 사용한다. 강제형변환과 자동형변환이 이에 해당한다.
자동형변환은 범위가 더 작은 타입에서 큰 타입으로 대입할 때 이루어진다. 범위가 더 큰 타입에서 작은 타입으로 가야하는 경우 강제형변환을 사용한다. 큰 범위에서 작은 범위로 가면, 그만큼 삭제되는 부분이 있어 꼭 필요한 게 아니면 강제형변환은 자제하는 것이 좋다.
3) 매개변수 다형성
선언할 땐 임의 데이터 타입을 사용하지만, 메서드를 사용할 땐 구체적인 타입으로 지정하는 것을 매개변수 다형성이라고 한다. Generic 개념이 여기에 포함된다.
자바에서 List 객체를 생성할 때 List<T>
의 T안에 정수, 실수, 문자열, 클래스 등 어느 타입을 넣어도 리스트로 생성할 수 있다. List<Class>
를 넣으면 해당 리스트는 인스턴스 되면서 각 객체가 리스트에 들어가게 된다. 이 List를 포함하여 매개변수가 여러가지가 올 때를 대비해 제네릭을 사용한다. 이는 매개변수 다형성이 이루어지는 것이다.
4) 포함 다형성
일반적으로 다형성을 말할 때 포함 다형성을 의미한다. 포함 다형성은 동일한 메시지를 전달해도 수신 객체에 따라서 실제 수행이 달라지는 것을 의미한다. 주로 인터페이스를 통해 상속받은 클래스 객체를 만들 때 이 특징이 뚜렷하게 나타난다.
서로 같은 인터페이스를 상속받은 클래스 A와 클래스 B가 있을 때, 두 클래스의 객체 a, b를 생성했다. 인터페이스를 통해 오버라이딩한 메서드 method()를 두 객체에서 모두 호출하려고 한다. 이때 method()를 호출한다는 메시지는 동일하지만, 실제 수행은 A의 method(), B의 method()로 달라진다. 이를 포함 다형성이라고 하며, 다형성을 설명할 때 일반적인 경우이다.
3. 캡슐화
위험한 접근으로부터 데이터를 보호하기 위해 접근을 제어하는 절차 개념을 의미한다. 쉽게 말해 데이터를 캡슐에 넣어 외부로부터 보호한다. 데이터에 접근할 땐 공용 메서드를 통해 접근하고, 서로 다른 객체의 데이터와 구별할 수 있다.
- 정보 은닉
정보 은닉이라는 개념도 객체 지향에서 자주 사용되는 단어이다. 캡슐화가 무엇인지 그 개념을 보고, 정보은닉이라는 이름은 보면 이 두개는 동일하다고 생각할 수도 있다. 개념은 비슷하지만 그 의미는 다르다. 캡슐화가 된 코드라고 해서 정보은닉이 된 코드라고 보지 않는다. 캡슐화는 지켜졌지만, 정보은닉이 되지 않을 수 있다. 외부로부터 보호한다는 성질은 같다. 정보은닉은 어떤 특징이 있을까?
정보은닉은 시스템을 분할하는 모듈 분할의 원리를 이용하여, 다른 객체 간 상호 연결성을 줄이기 위해 접근을 제한한다. 외부 인터페이스를 유지하고 클라이언트에 영향을 주지 않으면서 구현 세부 사항을 변경할 수 있을 때 정보은닉이 제대로 구현되었다고 본다. 내부 구현 사항을 노출시키지 않은 채로 사용자가 기능할 수 있게 하는 것. 추상화와 연결되는 개념으로도 알아볼 수 있다.
4. 추상화
세부적인 구현은 무시한 채 간단하게 표현할 수 있게 만드는 것을 추상화라고 한다. 상위 개념만 가지고 중요 개념을 표현한다. 실제 메시지가 어떤 타입으로 어떻게 구현되었는지 몰라도 정의된 메시지를 사용할 수 있을 때 추상화가 잘 되었다고 말한다.
ADT 추상 자료형 개념을 알아볼 때, 어떻게 구현이 되어있는지 그 방법은 명시하지 않고, 추상적으로 정의된다고 했다. 예를 들어 노크식 볼펜을 사용할 때, 볼펜 안에 스프링이 어떻게 연결되어 있는지 자세히 알고 사용하는 사람이 과연 많을까? 한번 누르면 볼펜심이 나오고, 어떻게 다시 한번 눌렀을 때 다시 들어갈까? 그 노크하는 동작 하나로 볼펜심을 넣었다 뺄 수 있는 원리를 알고 사용하는 사람이 얼마나 될까? 우리는 볼펜의 그 원리를 알지 못한다. 하지만 그저 노크를 하는 것만으로도 사용할 수 있다.
이렇게 사용자가 동작 방법, 그 내부가 어떻게 구현 설계 되었는지 알고 있지 않아도 그저 메시지 하나만으로 원하는 기능을 사용할 수 있게 만드는 것이 추상화이다.
객체 지향 프로그래밍을 설계할 때는 이와 같은 객체 지향 특징을 지켜야 한다.
더 나은 설계를 위한 5대 원칙 SOLID를 다음으로 알아볼 수 있다.
reference
조영호. 「오브젝트: 코드로 이해하는 객체지향 설계」. 위키북스(2019).
'Language > JAVA' 카테고리의 다른 글
[자바] 자바 가상 머신, JVM(Java Virtual Machine) 자세히 이해하기 (0) | 2025.01.13 |
---|---|
객체 지향 5대 원칙 - SOLID 이해하기 (0) | 2025.01.10 |
[자바/JAVA] 상속과 인터페이스의 관계, 인터페이스 잘 구현하기 (0) | 2025.01.08 |
[자바/JAVA] 자바로 그래프(Graph) 직접 구현해보기 -인접 행렬, 인접 리스트 (1) | 2025.01.03 |
[자바/JAVA] HashSet을 사용해서 정렬이 되는 이유 찾아보기 (2) | 2025.01.03 |