객체지향 프로그래밍이란
- 객체지향 프로그래밍이란 객체의 지역 보존과 캡슐화, 메세징, 동적 바인딩 3가지 특성을 지닌 프로그래밍 기법입니다.
객체의 지역 보존 및 캡슐화
- 객체들은 상태와 행동을 지니고 있으며 내부의 상태는 숨기고 외부로 행동 즉 메서드를 노출시킵니다.
- 객체들은 서로 메세지를 주고 받으며 프로그램을 실행시킵니다.
- 이를 통해 각 객체들의 소스코드들은 다른 객체들의 소스 코드들과 무관하게 작성되고 유지될 수 있습니다.
- 코드의 재사용성이 높아지고, 프로그램의 복잡성이 낮아집니다.
메세징
- 메세지를 보낸다는 말은 메서드를 호출한다는 의미입니다.
- Java에서의 메서드 호출은 static 메서드 호출, 생성자나 private 메서드 호출, 인스턴스 메서드 호출, 인터페이스 메서드 호출이 존재합니다.
동적 바인딩
- 인스턴스 메서드 호출과 인터페이스 메서드 호출과 실제로 연결되는 메서드는 프로그램의 실행 시점에 결정 됩니다.
- 이를 동적 바인딩이라고 합니다.
- 어떤 객체가 해당 메세지를 받느냐에 따라 메세지를 처리하는 방식이 달라집니다.
- 동적 바인딩을 통해 기존 클래스를 변경하지 않고 기능을 확장시킬 수 있습니다.
객체지향 프로그래밍의 키워드
객체, 클래스, 인스턴스
public class Point {
public int x = 0;
public int y = 0;
public Point(int a, int b) {
x = a;
y = b;
}
}
Point originOne = new Point(23, 94);
- 현실 객체 : 모든 객체들은 상태와 행동을 지닙니다. 현실 세계의 존재하는 모든 개념, 물체들은 객체가 될 수 있습니다.
- 소프트웨어 객체 : 객체는 상태를 필드에 저장하고, 행동을 메서드에 저장합니다. 내부의 상태를 숨기고 외부와의 소통을 메서드를 통해서만 하는것을 캡슐화 라고 합니다.
- 클래스 : 소프트웨어 객체의 설계도
- 인스턴스 : 메모리가 할당된 객체
- 객체는 클래스의 인스턴스 입니다.
- 클래스의 생성자 호출하면 메모리가 할당되고 해당 메모리의 주소가 반환됩니다.
상속, 다형성
- 상속 : 코드 재사용 및 코드 조직화를 위해 기존 클래스를 기반으로 새롭게 정의하는 것
- 다형성 : 같은 메세지더라도 메세지를 전달받는 객체에 따라 행동이 달라진다.
오버로딩, 오버라이딩
- 오버라이딩 : 상위클래스에서 정의한 메서드를 하위 클래스에서 재정의 하는 것입니다.
- 오버라이딩을 하기 위해서는 메서드의 리턴타입, 매개변수의 개수와 타입이 같아야 합니다.
- 오버로딩 : 메소드의 이름이 같고, 메소드의 매개변수의 개수 또는 타입이 다른 함수를 정의하는 것 - 새로운 함수로 리턴 타입은 관계 없습니다.
추상 클래스 인터페이스
- 추상 클래스 : 추상 클래스는 일종의 뼈대 입니다. 서브클래스들의 동일한 기능들은 추상 클래스에서 구현해두고 서브클래스에서 구현이 달라지는 부분들은 추상 메서드로 남겨 둡니다.
- 인터 페이스 : 인터페이스는 메세지를 보내는 방법만 정의한 것입니다. 인터페이스를 구현한 클래스는 해당 인터페이스에 정의된 행동을 제공할 것을 약속 합니다.
공통점
- 구현되지 않은 메서드를 가지고 있으며 이를 하위 클래스에서 구현하도록 합니다.
- 인스턴스화 시킬 수 없습니다.
차이점
- 추상클래스는 상속을 하고, 인터페이스는 구현을 합니다.
- 다중 상속을 불가능하나 다중 구현은 가능합니다.
- 추상클래스의 목적은 서로 관련 있는 하위 클래스의 공통된 메서드들은 추상클래스에서 구현하고, 하위 클래스에서 구현이 달라지는 메서드들은 하위 클래스에서 구현하도록 만드는 것입니다.
- 인터페이스의 목적은 서로 관련없는 클래스에서 특정 메서드를 구현하도록 만들 때 사용합니다.
- Java 8의 디폴트 메서드와 스태틱 메서드의 도입 이전에, 인터페이스의 필드는 public static final 변수만 선언할 수 있고, 모든 메소드들은 public합니다.
객체지향 설계의 5대 원칙 - SOLID
단일 책임 법칙
- SRP : 클래스는 한가지 이유만으로 변경을 해야합니다.
- List의 인터페이스의 구현체는 각각 AbstractList와 AbstractSequentialList로 구현됩니다.
- AbstractList : RandomAccess를 통해 데이터에 접근하는 리스트
- AbstractSequentialList : Sequential Access를 통해 데이터에 접근하는 리스트
- AbstractList는 ArrayList로 구현되며 AbstractSequentialList는 LinkedList로 구현 됩니다.
public E get(int index) {
Objects.checkIndex(index, size);
checkForComodification();
return root.get(offset + index);
}
public E get(int index) {
try {
return listIterator(index).next();
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
- 2가지 리스트는 데이터에 접근하는 방법을 다르게 구현합니다.
- 만약 2가지 클래스를 나누지 않고 하나의 클래스로 모든것을 구현했다면 RandomAccess의 세부 구현 사항이 바뀌거나, Sequential Access의 세부 구현 사항이 바뀔 때마다 해당 클래스를 변경해야 합니다.
- 2가지 클래스를 나눔으로써 RandomAccess의 세부 구현 사항을 바꿀 때는 AbstractList를 Sequential Access의 세부 사항을 바꿀 때는 AbstractSequentailList의 코드에 접근하면 됩니다.
- 이렇게 많은 책임을 지니고 있는 클래스는 프로그램의 복잡성을 증가시킵니다.
- 같은 이유에 의해 변경되는 것들은 모으고, 다른 이유에 의해 변경되는 것들은 분리해야 합니다.
개방 폐쇄 법칙
- OCP : 클래스를 수정하지 않고 기능을 확장할 수 있어야 합니다.
- Collections.sort() 함수는 컬렉션을 정렬시킵니다.
- Collections.sort()는 Comparable 인터페이스를 상속받은 객체들로 이루어진 리스트를 인자로 받습니다.
- 정렬을 수행하면서 최종적으로 binarySort()를 호출하고 이 때 리스트 안에 있는 객체들을 Comparable 인터페이스로 변환시키고 compareTo 함수를 호출합니다.
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
private static void binarySort(Object[] a, int lo, int hi, int start) {
assert lo <= start && start <= hi;
if (start == lo)
start++;
for ( ; start < hi; start++) {
Comparable pivot = (Comparable) a[start];
int left = lo;
int right = start;
assert left <= right;
while (left < right) {
int mid = (left + right) >>> 1;
if (pivot.compareTo(a[mid]) < 0)
right = mid;
else
left = mid + 1;
}
assert left == right;
...
}
- 기존 Collections class를 수정하지 않고도 Collections.sort()의 기능을 확장시킬 수 있습니다.
- Collections.sort()에게 전달되는 List 안에 있는 객체들의 종류에 따라 Collection.sort() 함수의 기능은 달라집니다.
- Integer 객체들이 주어지면 숫자들을 기준으로 비교하고 String 객체들이 주어지면 문자열을 기준으로 비교합니다.
- Comparable 인터페이스를 구현한 새로운 객체를 전달하면 사용자가 새롭게 정의한 compareTo() 함수를 호출 합니다.
- OCP 법칙이 가능한 것은 DIP 법칙이 지켜지고 있기 때문입니다.
리스코프 치환 법칙
- LSP : 하위클래스는 상위 클래스로 대체할 수 있어야 합니다.
- ArrayList는 AbstractList를 상속받고 있습니다.
- AbstractList는 데이터에 임의 접근을 하는 리스트입니다.
- 만약에 ArrayList에서 데이터에 접근하는 get()함수를 순차 접근으로 구현했다면 이는 리스코프 치환 법칙을 위반합니다.
- 왜냐하면 ArrayList는 AbstractList로 대체할 수 없기 때문입니다.
인터페이스 분리 법칙
- ISP : 구체적인 기능을 가진 여러가지 인터페이스를 만들어야 합니다.
- Collection 인터페이스는 List 인터페이스, Set 인터페이스, Queue 인터페이스로 분리됩니다.
- 이를 통해 각각의 인터페이스는 독립적으로 유지보수 될 수 있습니다.
- List : 순서가 존재하는 컬렉션
- Set : 중복되지 않는 원소들로 구성된 컬렉션
- Queue : FIFO 구조로 이루어진 컬렉션
의존관계 역전 법칙
- DIP : 구현체에 의존하는 것이 아니라 추상체에 의존해야합니다.
- Collections.sort() 함수에서 compareTo 메서드를 호출할 때 Comparable 인터페이스에 의존하고 있습니다.
- 이 때문에 Comparable 인터페이스를 상속받은 객체라면 자유롭게 해당 메세지를 받을 수 있습니다.
- 의존관계 역전 법칙으로 인해 개방 폐쇄 법칙을 실현시킬 수 있습니다.
Reference