객체 지향 프로그래밍(OOP)에 대해서는 10년 이상 보고 듣고 배웠는데 과연 나는 OOP에 대해 얼마나 잘 알까?
라는 의문으로 책 - 스프링 입문을 위한 자바 객체 지향의 원리와 이해을 읽으며 부족했던 부분을 채운 내용을 정리하는 글입니다.
스프링을 잘하기 위해서 자바의 기본을 다시 돌아보는 좋은 계기가 될 수 있을 것 같다.
객체 지향은 현실 세계를 반영한다.
기존 구조적 프로그래밍 언어에서 가장 중요한 것은 함수였는데 이후 나온 개념이 객체 지향이고, 주변에서 사물을 인지하는 방식대로 프로그래밍할 수 있기 때문에 직관적이다.
캡슐화(Encapsulation) - 정보 은닉
상속(Inheritance) - 재사용
추상화(Abstraction) - 모델링
다형성(Polymorphism) - 사용 편의
기존에 우리는 클래스와 객체를 설명할 때 붕어빵틀과 붕어빵으로 대부분 설명했다. 하지만 이 책에선 다른 시각으로 바라본다.
클래스 객체명 = new 클래스();
붕어빵틀 붕어빵 = new 붕어빵틀(); // = 새로운 붕어빵틀을 만들면 붕어빵이 만들어진다.
특정 사물 틀로 원하는 사물을 만든다.
를 클래스와 객체로 설명하기 위해 "틀"은 빠지지 않고 나왔다.
클래스는 분류, 객체는 사물이라는 관계로 설명하면 클래스 : 객체 = 펭귄 : 뽀로로 = 사람 : 김연아
처럼 나오게 된다. 즉, 어떤 개념이 클래스라면 실체가 되는게 객체라고 생각하면 된다.
더 자세한 내용은 해당 책의 추상화: 모델링을 통해 공부하는 것을 추천한다.
추상화의 개념을 넓게 보면 상속을 통한 추상화 & 구체화, 인터페이스를 통한 추상화, 다형성을 통한 추상화 등이 포함될 수 있다.
자바는 객체 지향의 추상화를 class 키워드를 통해 지원한다. (추상화 = 모델링 = 자바의 class 키워드)
클래스 객체_참조_변수 = new 클래스();
이 책을 읽고 정리가 필요하다. 라는 생각을 하게 된 가장 중요한 부분인 메모리 영역이다.
클래스를 설계하기 위해 객체의 공동 특성을 뽑아야 한다.
ex) 쥐의 공동 특성 : 성명, 나이, 꼬리수 / 행위 : 울다()
public class Mouse {
public String name;
public int age;
public int countOfTail;
public void sing() {}
}
public class MouseDriver {
public static void main(String[] args) {
Mouse mickey = new Mouse();
mickey.name = "미키";
mickey.age = 85;
mickey.countOfTail = 1;
mickey.sing();
Mouse jerry = new Mouse();
jerry.name = "제리";
jerry.age = 73;
jerry.countOfTail = 1;
jerry.sing();
}
}
객체 지향을 통해 Mouse라는 클래스(개념)가 있고, 개념들에 해당하는 mickey, jerry 객체(사물)들이 존재한다.
객체에 대해 이해했으면 메모리에서 어떻게 존재하는지 아는 것이 중요하다고 생각한다.
클래스
의 장소메서드
의 장소객체
의 장소이 3가지 순서는 가장 기본이지만 처음에는 제대로 이해하지 못한 채로 메서드와 객체들이 존재한다라고만 이해한 채로 개발을 진행하게 되는 것 같다. 지금까지 대략적으로만 이해하고 이번에야 정확하게 공부하면서 이해할 수 있었다.
또한 Mouse의 countOfTail이 항상 1이라면 불필요한 메모리를 사용하게 되므로 별도로 입력하기보다는 static을 통해 스태틱 영역에 저장하고 Mouse.countOfTail을 통해 접근할 수 있다.
상속이 부모 - 자식으로만 연결시킬 수 없다.
상위 클래스의 특성을 하위 클래스에서 상속하고 더 필요한 특성을 추가(확장)해서 사용할 수 있다.
우리는 클래스에서 상속을 inheritance가 아닌 확장의 extends를 사용한다.
클래스를 상속하는 뜻(부모-자식)보다는 클래스의 특성을 상속하므로
상위 클래스-하위 클래스 or 슈퍼 클래스-서브 클래스
로 표현하는 것이 더 좋다.
public class 동물 {
String myClass;
동물() { myClass = "동물" }
void showMe() { System.out.println(myClass); }
}
public class 포유류 extends 동물 {
포유류() { myClass = "포유류"; }
}
public class 조류 extends 동물 {
조류() { myClass = "조류"; }
}
public class 고래 extends 동물 {
고래() { myClass = "고래"; }
}
public class 박쥐 extends 동물 {
박쥐() { myClass = "박쥐"; }
}
public class 참새 extends 동물 {
참새() { myClass = "참새"; }
}
public class 펭귄 extends 동물 {
펭귄() { myClass = "펭귄"; }
}
// (1)
동물 animal = new 동물(); // animal.showMe();
포유류 mammalia = new 포유류(); // mammalia.showMe();
조류 bird = new 조류(); // bird.showMe();
고래 whale = new 고래(); // whale.showMe();
박쥐 bat = new 박쥐(); // bat.showMe();
참새 sparrow = new 참새(); // sparrow.showMe();
펭귄 penguin = new 펭귄(); // penguin.showMe();
// (2)
동물 animal = new 동물(); // animal.showMe();
포유류 mammalia = new 포유류(); // mammalia.showMe();
조류 bird = new 조류(); // bird.showMe();
고래 whale = new 고래(); // whale.showMe();
박쥐 bat = new 박쥐(); // bat.showMe();
참새 sparrow = new 참새(); // sparrow.showMe();
펭귄 penguin = new 펭귄(); // penguin.showMe();
// (3)
동물[] animals = new 동물[7];
animals[0] = new 동물();
animals[1] = new 포유류();
animals[2] = new 조류();
animals[3] = new 고래();
animals[4] = new 박쥐();
animals[5] = new 참새();
animals[6] = new 펭귄();
for (int index = 0; index < animals.length; index++) {
animals[index].showMe();
}
(1), (2), (3) 모두 동일한 결과가 나오는데 주로 (1)로만 쓰게 되다보니 지금까지 제대로 활용하지 못 했다는 생각이 들었다.
자바는 다중 상속 대신 인터페이스를 도입했다.
public class Animal {
public String name;
public void showName() { ... }
}
public class Penguin extends Animal {
public String habitat;
public void showHabitat() { ... }
}
public class Driver {
public static void main(String[] args) {
Penguin pororo = new Penguin();
pororo.name = "뽀로로";
pororo.habitat = "남극";
pororo.showName();
pororo.showHabitat();
Animal pingu = new Penguin();
pingu.name = "핑구";
// pingu.showHabitat();
}
}
오버라이딩(overriding)
같은 메서드 이름, 같은 인자 목록으로 상위 클래스의 메서드 재정의
오버로딩(overloading)
같은 메서드 이름, 다른 인자 목록으로 다수의 메서드 중복 정의
오버라이딩은 기존 메서드 위에 새로 쓰기 때문에 라이딩 = 재정의, 오버로딩은 이름은 같지만 다른 인자 목록으로 여러 개를 불러올 수 있어 로딩 = 중복으로 생각하면 헷갈리지 않는다.
public class Animal {
public String name;
public void showName() { ... }
}
public class Penguin extends Animal {
public String habitat;
public void showHabitat() { ... }
// 오버라이딩
public void showName() { ... }
// 오버로딩
public void showName(String yourName) { ... }
}
public class Driver {
public static void main(String[] args) {
Penguin pororo = new Penguin();
pororo.name = "뽀로로";
pororo.habitat = "남극";
pororo.showName();
pororo.showName("친구");
pororo.showHabitat();
Animal pingu = new Penguin();
pingu.name = "핑구";
pingu.showName();
}
}
Animal 객체 = new Penguin();
의 경우 객체는 Animal이 가진 정보만 사용할 수 있다. 근데 이 경우에 Animal.showName()이 실행되는 것이 아닌 Penguin에서 재정의한 메서드가 실행되게 된다.동물[] 동물들 = new 동물[5];
동물들[0] = new 쥐();
동물들[1] = new 고양이();
동물들[2] = new 강아지();
동물들[3] = new 송아지();
for (int i = 0; i < 동물들.length; i++) {
동물들[i].울기();
}
상위 클래스에 해당하는 동물의 메서드 울기()를 실행하면 하위 클래스 쥐, 고양이, 강아지, 송아지에서 재정의된 울기()가 자동으로 실행되게 된다.
상속과 다형성을 잘 이용하면 우린 상위 클래스를 통해 필요한 하위 클래스의 역할을 잘 실행할 수 있고 이것으로 Spring의 PSA를 잘 활용할 수 있을 것이다.
접근 제어자 : private, [default], protected, public
접근 제어자가 객체(인스턴스) 멤버와 쓰일 때 / 정적(클래스) 멤버와 함께 쓰일 때 비교해봐야 한다.