OOP Features
Encapsulation & Abstraction
가독성을 위한 OOP의 핵심 feature.
- Encapsulation: 특정한 하나의 목적을 위해 필요한 data와 method를 묶어 객체로 만드는 것. 객체지향 프로그래밍은 객체 단위로 프로그램을 구성하기 때문에 객체 내부의 구체적인 데이터는 외부에서 직접 접근할 필요가 없고, 접근할 수도 없어야 한다.
- Abstraction: 객체의 구체적인 구현은 외부에 드러내지 않고 객체와 그 행동을 추상적 개념화하는 것. 이를 위해 객체 내부의 데이터에 대한 operation은 오로지 객체가 제공하는 method를 통해 이루어져야 한다.
두 개념은 비슷하지만, encapsulation이 abstraction을 실현하는 수단이다.
Inheritance
코드의 재사용성을 위한 OOP의 핵심 feature. 한 클래스가 다른 클래스를 확장하는 경우 상속을 통해 코드의 중복을 피할 수 있다.
그러나 상속은 여러가지 문제점을 가지고 있어 구성으로 대체하는 추세다. (Composition over inheritance)
Breaks Encapsulation
당연하지만 상속은 캡슐화를 깨트린다. 자식 클래스가 부모 클래스를 확장하는 것이기 때문에 부모 클래스를 하나의 개념으로서 추상화할 수 없고 내부 구현을 알아야 하는 경우가 생긴다. 또한 이로 인해 기존 코드가 의도한 대로 동작하지 않을 수 있다.
1
2
3
4
5
6
7
8
9
10
11
public class SimpleList<T> {
/**
* Add item to the list.
*/
public void addItem(T item) { /* ... */ }
/**
* Add all items to the list.
*/
public void addAllItems(Collection<? extends T> items) { /* ... */ }
}
단순한 리스트를 생각해보자. 이 리스트는 단일 item을 추가하거나 여러 item들을 한 번에 추가할 수 있다.
이제, 단순 리스트를 상속하는 확장된 리스트를 생각해보자.
확장된 리스트는 numItems
라는 필드를 가지고 있어 전체 item 수를 return할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ExtendedList<T> extends SimpleList<T> {
// Number of items.
private int numItems;
@override
public void addItem(T item) {
this.numItems++;
return super.addItem(item);
}
@overrride
public void addAllItems(Collection<? extends T> items) {
this.numItems += items.size();
return super.addAllItems(items);
}
public int getNumItems() {
return this.numItems;
}
}
이 코드는 문제가 있다. ExtendedList
만 보면 item 수를 정상적으로 tracking하는 것 같지만,
만약 BaseList
의 addAllItems
메소드가 addItem
을 호출한다면 중복 counting이 된다.
만약 line 13을 제거한다고 해도 BaseList
의 addAllItems
메소드가 addItem
을 호출하지 않는다면 counting을 하지 않게 된다.
따라서 ExtendedList
를 구현하는 개발자는 BaseList
의 내부 구현을 알아야만 한다.
이렇게 상속은 자식 클래스가 부모 클래스에 강하게 의존하기 때문에 코드의 유연성, 확장성을 해치는 결과로 이어진다.
Composition
상속은 구성으로 대체할 수 있다. 구성은 한 클래스가 다른 클래스의 인스턴스를 attribute로 가지는 구조를 말한다. attribute의 메소드를 호출함으로써 코드의 재사용을 피할 수 있다.
Polymorphism
Language Theory와 Type Theory에서 다형성은 하나의 symbol로 여러 type을 나타내는 것을 말한다.
예를 들면, 공통의 인터페이스를 이용하여 이를 구현하는 클래스(객체)의 type을 표현하거나, 부모 클래스를 이용하여 자식 클래스까지도 나타낼 수 있다.
이런 것들을 Polymorphic type이라고도 한다.
Subtyping
S가 T의 subtype일 때 임의의 context에서 type이 T인 term을 type이 S인 term으로 대체할 수 있다. 그러나 의미적으로 올바른지는 알 수 없다. 한편, Liskov Substitution Principle은 Subtyping의 의미론적 올바름까지 확장한 것이다.
간혹 subtyping이 존재하는 경우 type S가 type T에 포함되는 것으로 보기도 하는데, 이 경우 각 term은 두 개 이상의 type을 가질 수 있게 된다.
Parametric Polymorphism
Generic은 arbitrary type으로 대체할 수 있는 abstract symbol이다. Subtyping은 클래스의 다형성으로 보다 프로그래밍적인데 반해, 이는 abstract symbol에 대한 polymorphism으로 보다 language theory적이다.