생각해보니 자바의 장단점 이후에 객체 지향 프로그래밍에 대해 한 번 정리하려고 했었는데, 당장 궁금한 것들이 너무 많아서 우선순위에서 밀려있었었다.
그래서 생각난 김에 간단하게 정리를 해보려고 한다.
JAVA is A P. I. E
수업 때 위와 같이 외우면 잘 외워진다고 해서 메모해둔 '자바는 파이이다 !' 이다.
APIE가 객체지향 프로그래밍의 대표적인 개념들이다.
Abstraction (추상화)
객체들의 공통된 특성을 파악하여 정의해두는 것
예를 들어 자동차의 경우 기본적으로 움직이고 멈추는 것은 공통적인 특성이라고 할 수 있다.
하지만 세부적으로는 어떤 방식으로 움직이는지 멈추는지는 각 회사의 차량마다 다를 것이다.
그래서 공통된 특성을 일단 추상화해서 정의해두는 것을 의미한다고 생각하면 된다.
위에서 설명한 자동차를 간단히 작성한 코드이다.
public abstract class Car {
public abstract void go();
public abstract void stop();
public void run() {
System.out.println("부모 달린다 !!");
}
public void test() {
System.out.println("===== 테스트시작 =====");
go();
stop();
run();
System.out.println("===== 테스트종료 =====");
}
}
Inheritance(상속)
P가 아니라 I를 먼저 적어둔 까닭은 위 코드와 연달아서 설명하기 위함이다.
작성된 코드를 이용해 실제 움직이는 차량을 만든다고 해보자.
기본적으로 움직이고 멈추긴 하지만 해당 차의 메커니즘은 다르다고 가정하기 위해 전기차와 일반 휘발유 차량을 정의하고 위에서 추상화한 Car 클래스를 상속받아 세부 기능을 작성해보았다.
그리고 이렇게 구성이 되었을 때
상속을 해주는 Class 인 Car 는 부모 클래스라고 부르며 그 외에 조상, 상위, 슈퍼 클래스라고도 부르고,
상속을 받는 Class 인 전기차, 휘발유차는 자식 클래스라고 부르며 그 외에 자손, 하위, 서브 클래스라고도 한다.
해당 코드는 아래와 같다.
public class ElectricCar extends Car {
@Override
public void go() {
System.out.println("전기 모터를 이용해 움직임");
}
@Override
public void stop() {
System.out.println("브레이크를 사용해 멈춤");
}
@Override
public void run() {
System.out.println("자식 달린다 !!");
}
}
public class GasolineCar extends Car {
@Override
public void go() {
System.out.println("휘발유로 엔진모터를 작동시켜 움직임");
}
@Override
public void stop() {
System.out.println("브레이크를 사용해 멈춤");
}
}
만약 상속 관계에서 부모의 메서드를 자식이 Override 했다면 Override 한 메서드가 출력된다.
왜냐하면 자식에서 더 좋게 사용하기 위해 부모의 메서드를 Override 했다고 판단하기 때문이다.
작성된 코드들을 실행해보는 테스트 클래스를 통해 확인해보자.
public class AbstractionTest {
public static void main(String[] args) {
Car car1 = new ElectricCar();
Car car2 = new GasolineCar();
car1.test();
car2.test();
}
// 실행결과
// ===== 테스트시작 =====
// 전기 모터를 이용해 움직임
// 브레이크를 사용해 멈춤
// 자식 달린다 !!
// ===== 테스트종료 =====
// ===== 테스트시작 =====
// 휘발유로 엔진모터를 작동시켜 움직임
// 브레이크를 사용해 멈춤
// 부모 달린다 !!
// ===== 테스트종료 =====
}
실행결과의 자식 달린다 부분이 왜 나왔는지 궁금하다면 Car와 ElectricCar의 run() 메서드를 살펴보면 된다.
여기까지의 내용이 추상화와 상속에 관한 내용이다.
말보다는 코드로 보는 게 이해가 쉬울 것 같아 간단한 코드들을 적어보았는데 도움이 되었으면 좋겠다.
Polymorphism(다형성)
하나의 객체가 많은 형질을 가지는 성질
상속 관계에서 부모 클래스의 타입으로 자식 클래스의 인스턴스를 참조할 수 있다.
예시로 부모 클래스로 Person을 생성한 뒤에 먹고 점프하기를 메서드로 구성해보았다.
public class Person {
public void jump() {
System.out.println("두 다리로 폴짝~~");
}
public void eat() {
System.out.println("냠냠..");
}
}
그리고 자식 클래스인 SpiderMan은 추가로 거미줄을 쏘고, 먹고 점프하는 메서드를 Override 해서 바꾸었다.
// Person을 상속 받는다 : is a 관계
public class SpiderMan extends Person {
public void fireWeb() {
System.out.println("거미줄 쉭쉭..");
}
@Override
public void jump() {
System.out.println("엄청 높이 점프 !!");
}
@Override
public void eat() {
System.out.println("꿀꺽");
}
}
이다음에 위의 코드들을 이용해 다형성을 테스트해보았다.
자세한 내용은 주석을 적어두었다.
public class PolyTest {
public static void main(String[] args) {
SpiderMan sman = new SpiderMan();
// 상속의 관계에서 조상으로 자식을 참조하는 것 - polymorphism
// SpiderMan 속성만 볼 수 있음
SpiderMan sman2 = sman;
// Person 속성만 볼 수 있음
Person p = sman;
// Object 속성만 볼 수 있음
Object obj = sman;
// 메모리에 있더라도 참조하는 변수의 타입에 따라 접근할 수 있는 내용이 제한됨
// 명시적 캐스팅
// SpiderMan 속성을 모두 사용가능해짐
SpiderMan sman3 = (SpiderMan)p;
// 부적절한 형변환
// 아래와 같이 하면 오류 발생
// 생성은 Person만 해두었기때문에 SpiderMan에 대한 정보가 메모리에 없어서
// 오류가 발생한다. 문법적으로는 오류가 없고 실행시 ClassCastException 발생
Person p2 = new Person();
// instanceof 로 실제 메모리에 있는 객체의 타입을 확인
if (p2 instanceof SpiderMan) {
SpiderMan sman4 = (SpiderMan)p2;
sman4.fireWeb();
}
}
}
위 코드에서 instanceof 는 왼쪽에 전달된 참조 변수가 실제로 참조하고 있는 인스턴스 타입이 오른쪽에 전달된 클래스 타입이면 true를 반환하고, 아니면 false를 반환하는데, 이를 통해 위의 코드처럼 확인 후에 명시적으로 캐스팅할 수 있다.
묵시적 캐스팅 : 작은 집에서 큰 집으로 갈 때
명시적 캐스팅 : 큰 집에서 작은 집으로 갈 때
추가적으로 Object는 모든 클래스의 조상이기 때문에 Object 배열은 어떤 타입의 객체라도 다 저장할 수 있음.
기본형은 Warpper Classs로 자동 변환해서 담음 (ex : int -> Integer)
Object로 파라미터를 받으면 모든 타입의 객체를 받을 수 있어 활용도가 높아지지만 코드의 복잡성도 함께 증가한다.
그래서 프로젝트 레벨에서는 비즈니스 로직 상 최상위 객체를 사용하는 것을 권장한다.
Encapsulation(캡슐화)
은닉과 보호의 역할
데이터 은닉과 보호를 위해 private로 객체를 생성하고 getter, setter를 통해 접근하게끔 설정
이번에도 간단한 코드를 통해 알아보자.
아래 코드와 같이 구성하면 외부에서는 getter와 setter를 통해 클래스 내부의 num과 id를 확인하거나 변경할 수 있다.
public class Encapsulation {
private int num;
private String id;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
캡슐화 중 대표적으로 Singleton 디자인 패턴이 존재한다.
- 객체의 생성을 제한해 하나의 객체만을 사용 (수정 가능한 멤버 변수가 없고 기능만 있는 경우)
- 외부에서 생성자에 접근 금지 -> 생성자의 접근 제한자를 private으로 설정
- 내부에서는 private에 접근 가능하므로 직접 객체 생성 -> 멤버 변수 이므로 private 설정
- 외부에서 private member에 접근할 수 있도록 getter 생성 -> setter는 불필요
- 객체 없이 외부에서 접근할 수 있도록 getter와 변수에 static 추가
- 외부에서는 언제나 getter를 통해서 객체를 참조하므로 하나의 객체를 재사용함
간단하게 OOP에 관해서 정리해보려고 했는데, 예전에 개념들을 이해하면서 작성해둔 코드들이 있어서 같이 첨부해서 작성해보았다.
이 글을 작성하면서 나도 다시 개념을 제대로 이해하게 된 것 같아서 좋았고, 다른 누군가도 보고 이해했다면 더 좋을 것 같다.
'Computer Science > JAVA' 카테고리의 다른 글
자바 컬렉션 프레임워크(Java Collection Framework) 중 맵(MAP) 정리 (0) | 2022.04.18 |
---|---|
자바 컬렉션 프레임워크(Java Collection Framework) 중 셋(SET) 정리 (0) | 2022.04.15 |
자바 컬렉션 프레임워크(Java Collection Framework) 중 리스트(List) 정리 (0) | 2022.04.13 |
JAVA의 장점과 단점 (1) | 2021.12.20 |
JAVA 란? (0) | 2021.12.16 |