객제지향 프로그래밍의 탄생 배경
객체지향 이전에는 프로그래밍을 개발 시 다음과 같은 문제점들이 있었다
- 데이터와 함수의 분리: 데이터와 함수가 각각 분리되어 있었기 때문에 체계적으로 조작하기 어려웠다
- 사람의 사고방식과 다른 코드: 사람이 이해하는 방식으로 코드를 작성하는 것이 아닌, 컴퓨터가 돌아가는 방식대로 코드를 작성하다보니 코드를 직관적으로 이해하기 어려웠다
- 코드의 복잡성 : 프로그램이 커지면 커질수록 복잡도가 크게 증가했다
- 유지보수와 확장성의 어려움: 코드가 복잡해지고 이해하기 어려워지다 보니 개발/기능 수정/유지보수 등 다양한 방면에서 비용과 시간이 많이 소요되었다
객체지향 프로그래밍(Object-Oriented Programming, OOP)이 발전하게 된 배경은 위와 같은 문제점들을 해결하기 위한 노력의 일환이다
객체지향 프로그래밍이란?
- 객체지향 프로그래밍 = Objected Oriented Programming = OOP
- 여러가지 프로그래밍 패러다임 중 하나
- 데이터와 이 데이터를 처리하는 함수를 하나의 ‘객체’로 묶는 방식을 중심으로 프로그램을 구성하도록 하는 패러다임
- 절차지향 프로그래밍이 너무 어렵기 때문에 사람이 생각하는 방식과 유사하게 프로그래밍 할 수 있게 하고자 하는 생각에서 나온 개념
- 단, 모든 프로그램은 최종적으로는 절차지향적으로 수행된다. 따라서 절차지향 프로그래밍이 아니라고 생각하면 안된다
- 프로그램을 구성하는 기본 요소를 객체로 보고 하는 객체들이 서로 상호작용을 하여 프로그램이 돌아가게 만들려는 노력
- 주요 특징으로 캡슐화, 상속, 다형성, 추상화가 있다.
캡슐화(Encapsulation)
알려줄 필요가 없는 데이터는 숨겨라
- 데이터 구조와 데이터를 다루는 방법들을 결합 시켜 묶는 것
- 변수와 함수를 하나로 묶는 것을 뜻함
- 내부의 데이터를 외부로부터 보호
- 접근제어자 (
private
,default
,protected
,public
)
- 접근제어자 (
- 접근제어자를 통해 정보은닉을 활용 할 수도 있다
- 정보은닉 : 객체 안에 있는 데이터를 외부로부터 보호
- 보호된 정보에 대해서
getter
,setter
메서드를 만들어 접근 가능하게 하곤 한다
- 사용자가 클래스 속을 알 필요가 없음
- 사용자가 함수 속을 알 필요가 없는 것과 마찬가지
- 이 개념은 추강화로 이어진다
- 함수를 분리할 때 적용했던 원칙을 클래스에서도 적용할 것
- 중복된 코드가 있다면
private
메서드로 분리
- 중복된 코드가 있다면
- 실용적인 용도 : 사용자측의 변경 없이 구현부를 변경할 수 있게 해준다
접근제어자 |접근 제어자|같은 클래스의 멤버|같은 패키지의 멤버|자식 클래스의 멤버| 그 외의 영역 | |—|—|—|—|—| |public|○|○|○| ○ | |protected|○|○|○| X | |default|○|○|X| X | |private|○|X|X| X | — getter/setter 함수를 통한 데이터 접근의 객관적인 장범
- 멤버 변수를 저장하지 않고 필요할 때마다 getter에서 계산 가능
- setter에서 추가적인 로직을 실행할 수 있음
- 상속을 통한 다형성 구현 가능
상속(Inheritance)
기존 클래스의 기능을 확장해서 재사용하라
- 이미 존재하는 객체를 기반으로 확장된 객체를 만드는 방법
- 엄밀히 말하면 객체가 아니라 클래스
- 거의 모든 사람이 OOP 핵심이라 여기는 특성
- 특히, 재사용성이 궁극의 목적이라 신봉하던 시대에 특ㅎ
- 현재에도 상속을 지원하지 않으면 OO 언어라고 안 보는게 보통
- OOP의 또 다른 매우 중요한 특성인 다형성의 기반
- 한 번에 모든 구체적인 사항을 만들지 말고 점진적으로 기능을 확장시켜 나갈 수 있게 해준다
- 사람에게는 점진적 학습이 가장 효율적이다
- 확장된 객체
- 기존의 객체에 속한 데이터와 동작을 모두 물려받음 (유전)
- 여기에 다른 데이터나 동작을 추가할 수 있음 (진화)
- 물론 새클래스를 상속해서 또 다른 새 클래스를 만들 수 있음
- 실용적인 용도 : 코드 중복을 막음
- 여러 객체에는 공통되는 데이터와 동작을 부모 객체로 만듦
- 여러 객체는 각각 그 부모 객체를 상속 받음
- 그 후 자기에게만 필요한 데이터나 동작을 추가
super 키워드
- super는 현 개체의 부모 부분을 가리킴
- super() 라고 코드를 작성하면 부모의 생성자를 호출
- 멤버 변수나 메서드를 호출할 때도 가능
다형성(Polymorphism)
동일한 인터페이스에 대해 여러 형태의 구현을 가능하게 하라
- 많은 사람들이 OOP의 핵심이라고 여기는 특징
- 같은 지시를 내렸는데 다른 종류의 객체가 동작을 달리 하는 것
- 같은 지시 : 동일한 함수 시그내처 호출
- 달리 동작 : 객체의 종류에 따라 실제로 실행되는 함수 구현 코드가 다름
- 절차적 언어에서 이런 일을 하려면
if
문을 사용해야 했다
- 어떤 함수 구현이 실행될지는 실행 중에 결정된다
- 이를 늦은 바인딩이라고 함 (late binding)
- 일반적인 함수 호출은 이른 바인딩 (컴파일 중에 결정된다)
- 다형성의 혜택을 받으려면 상속 관계가 필요
- 상속 없이는 다형성이 불가능하다
- 부모 객체에서 함수 시그내처를 선언
- 자식 객체에서 그 함수를 다르게 구현 (overriding)
- 실용적인 용도
- 다른 종류의 객체를 편하게 저장 및 처리 가능
오버라이딩(Overriding)
- 하위 클래스가 이미 상위 클래스에 정의된 메서드의 자체 구현을 제공할 수 있도록 하는 기능
- 오버라이딩 된 메서드를 호출 시 부모 클래스 구현 대신 자식 클래스 구현이 실행된다
- 하위 클래스 메서드에서
@Override
어노테이션을 명시적으로 사용하여 오버라이딩 메서드임을 명시할 수 있다
다형성의 장점
추상화(Abstraction)
시스템의 설계와 구현을 명확하게 구분하라
- OOP에서 추상화란 어떤 구체적인 것에 직접 손대지 않겠다는 것을 의미
- 객체 속에 있는 실제 데이터나 함수 구현 방법에 종속되지 않겠다는 뜻
- 데이터 추상화
- 객체 사용 시 그 안에 정확히 어떤 데이터가 있는지 알 필요 없음
- 객체 안에 있는 데이터에 접근 불가
- 그 대신 객체의 함수를 통해 접근
- 즉, 캡슐화는 추상화를 이루는 방법 중 하나
- 실용적인 용도 : 설계와 구현의 분리
추상 자료형쪽 관점
- 사용자는 클래스를 자료형으로 사용할 수 있음
- 그 클래스 안에 들어있는 멤버 변수가 정확히 뭔지 몰라도 됨
절차적 데이터 추상화쪽 관점
- 데이터를 직접 조작하는 대신 메서드를 호출
추상화의 단점
- 동작 없이 데이터만 있는 클래스는 쓸데없는 코드만 늘어남
- 어떻게 추상화를 해야할 지 뚜렷한 객관적 기준이 없음
예시로 이해하기
Person.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 추상화: 일반 개념을 나타내는 추상 클래스 정의
// 캡슐화: 개인 필드와 공용 메서드를 사용하여 데이터에 대한 액세스 제어
abstract class Person {
// 캡슐화: `protected` 접근제어라를 사용하여 필드에 대한 액세스 제어
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
// 다형성/추상화: 하위 클래스에 의해 구현되는 추상 메서드
public abstract void speak();
}
Korean.java
1
2
3
4
5
6
7
8
9
10
11
12
// 상속: Person 클래스를 확장하여 보다 구체적인 클래스 생성
class Korean extends Person {
public Korean(String name, int age) {
super(name, age);
}
// 상속: Korean 클래스에 대한 speak() 메소드 오버라이딩
@Override
public void speak() {
System.out.println("안녕하세요! 저는 한국인입니다.");
}
}
American.java
1
2
3
4
5
6
7
8
9
10
11
12
// 상속: Person 클래스를 확장하여 보다 구체적인 클래스 생성
class American extends Person {
public American(String name, int age) {
super(name, age);
}
// 상속: American 클래스에 대한 speak() 메서드 재정의
@Override
public void speak() {
System.out.println("Hello! I am an American.");
}
}
Main.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
public static void main(String[] args) {
Person koreanPerson = new Korean("홍길동", 30);
Person americanPerson = new American("John til", 25);
List<Person> people = new ArrayList<>();
people.add(koreanPerson);
people.add(americanPerson);
// 같은 메서드를 호출했지만, 객체마다 다른 결과를 출력 -> 다형성
for (Person person : people) {
System.out.println(person.getName() + " is " + person.getAge() + " years old.");
person.speak();
}
}
}
Comments powered by Disqus.