
객체 지향 프로그래밍(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
접근 제어자가 객체(인스턴스) 멤버와 쓰일 때 / 정적(클래스) 멤버와 함께 쓰일 때 비교해봐야 한다.
