앞에서 배운 상속과 다형성을 활용하면 프로그램을 유지보수하는 데 매우 편리하다.
이때 배열을 함께 사용하면 여러 하위 클래스 자료형을 상위 클래스 자료형으로 한꺼번에 관리할 수도 있다.
일반 고객과 VIP 고객의 중간 등급 만들기
예제 시나리오
고객이 늘어 VIP 고객만큼 물건을 많이 구매하지는 않지만, 그래도 단골인 분들에게 혜택을 주고 싶다.
그래서 GOLD 고객 등급을 하나 추가한다.
GOLD 고객의 혜택은 다음과 같다.
- 제품을 살 때는 항상 10% 할인해 준다.
- 보너스 포인트를 2% 적립해 준다.
- 담당 전문 상담원은 없다.
이 등급의 고객은 VIP 고객과 보너스 포인트 적립률이 다르고 담당 상담원도 없다.
이 내용을 기반으로 Customer 클래스를 상속받아 GoldCustomer 클래스를 만들어 보겠다.
고객 관리 시스템은 다음과 같은 계층 구조로 확장된다.

package polymorphism;
public class GoldCustomer extends Customer{
double saleRatio;
public GoldCustomer(int customerID, String customerName){
super(customerID, customerName);
customerGrade = "GOLD";
bonusRatio = 0.02;
saleRatio = 0.1;
}
public int calcPrice(int price){
bonusPoint += price * bonusRatio;
return price - (int)(price * saleRatio);
}
}
GoldCustomer 클래스는 지불 가격과 보너스 포인트를 계산하는 calcPrice() 메서드만 재정의했다.
이처럼 상속을 사용하면 새로운 기능이 추가되더라도 쉽게 구현할 수 있다.
배열로 고객 5명 구현하기
이제 여러 등급의 고객을 한번에 관리할 수 있도록 구현해 본다.
예제 시나리오
이 회사의 고객은 현재 5명이다.
5명 중 VIP가 1명, GOLD 2명, SILVER 2명이다.
이 고객들이 각각 만원짜리 상품을 구매했을 떄의 결과를 출력한다.
고객 인스턴스가 총 5개이므로 배열에 넣어서 관리하면 편리하다.
객체 배열 ArrayList는 자료형을 지정하여 선언해야 한다.
우리가 사용할 클래스는 Customer, GoldCustomer, VIPCustomer 세 종류이다.
배열의 자료형을 Customer로 지정하고, VIPCustomer 클래스와 GoldCustomer 클래스 모두 Customer에서 상속받은 클래스이므로 Customer형으로 선언한다.
이렇게 선언하면 Customer, GoldCustomer, VIPCustomer를 모두 사용할 수 있다.
그리고 이 배열에 Customer 하위 클래스의 인스턴스가 추가될 때 모두 Customer형으로 묵시적 형 변환이 된다.
ArrayList<Customer> customerList = new ArrayList<Customer>();
package witharraylist;
import java.util.ArrayList;
public class CustomerTest {
public static void main(String[] args){
ArrayList<Customer> customerList = new ArrayList<>();
Customer customerLee = new Customer(10010, "이승민");
Customer customerShin = new Customer(10020,"신승민");
Customer customerHong = new GoldCustomer(10030, "홍승민");
Customer customerYoul = new GoldCustomer(10040, "이민승");
Customer customerKim = new VIPCustomer(10050, "김승민", 12345);
customerList.add(customerLee);
customerList.add(customerShin);
customerList.add(customerHong);
customerList.add(customerYoul);
customerList.add(customerKim);
System.out.println("====== 고개 정보 출력 ======");
for(Customer customer : customerList){
System.out.println(customer.showCustomerInfo());
}
System.out.println("++++++ 할인율과 보너스 포인트 계산 ++++++++");
int price = 10000;
for(Customer customer : customerList){
int cost = customer.calcPrice(price);
System.out.println(customer.getCustomerName() + "님이 " + cost + "원 지불하셨습니다.");
System.out.println(customer.getCustomerName() + " 님의 현재 보너스포인트는 " + customer.bonusPoint + "점입니다.");
}
}
}

Customer 형으로 객체 배열 ArrayList를 선언하였다.
Customer 클래스와 하위 클래스 VIPCustomer, GoldCustomer의 인스턴스를 ArrayList에 추가했다.
첫번째 향상된 for문을 사용하여 고객 정보를 출력한다.
고객 정보를 출력하는 showCustomerInfo() 메서드는 재정의하지 않았으므로 Customer 클래스에 구현된 메서드가 호출될 것이다.
두번째 향상된 for문으로 각 고객이 지불해야 할 금액과 적립된 보너스 포인트를 출력한다.
고객 등급에 따라 할인율과 적립금이 다르므로 calcPrice() 메서드는 각 클래스에 재정의했다.
for(Customer customer : customerList) 문장은 customerList 배열의 요소를 하나씩 가져와서 Customer형 변수에 넣는다.
고객 정보를 ArrayList 배열에 저장할 때 Customer형으로 형 변환을 하여 추가했기 때문에 배열 요소를 하나씩 가져올 때도 Customer형으로 가져오게 된다.
그리고 각 인스턴스가 calcPrice() 메서드를 호출하면 현재 이 변수의 실제 인스턴스가 무엇이냐에 따라 재정의한 메서드를 각각 호출하여 계산한다.
이것이 다형성이다.
상속은 언제 사용할까?
VIP 고객 등급을 추가하는 문제를 다시 생각해 본다.
이미 Customer 클래스가 구현되어 있는데 추가 요구 사항이 생긴 것이다.
가장 간단하게는 Customer 클래스가 존재하니 여기에 추가 내용을 함께 구현할 수도 있다.
그런데 추가 기능을 이렇게 구현하면 코드가 굉장히 복잡해진다.
그 이유는 일반 등급 고객이 사용하지 않는 속성(상담원 ID, 할인율 등) 뿐만 아니라 VIP 고객만을 위한 서비스 내용까지 추가해야 하기 때문이다.
- 만약 Customer 클래스에 모든 등급의 내용을 넣어 구현한다면?
if(customerGrade == "VIP") {
//할인해 주고, 적립 많이
}
else if(customerGrade == "GOLD"){
//할인해 주고, 적립 적당히
}
else if(customerGrade == "SILVER"){
//적립만 해준다.
}
이런 경우 고객의 등급이 하나라도 추가되거나 삭제되면 유지보수가 매우 복잡해진다.
상속을 사용하면 모든 등급에서 공통으로 사용하는 코드 부분은 상위 클래스인 Customer 클래스에 구현하고, 각 등급별 고객의 내용은 각각의 하위 클래스에 구현한다.
또한 새로운 등급의 고객이 추가되더라도 기존의 코드를 거의 수정하지 않고 새로운 클래스를 추가할 수 있다.
따라서 프로그램이 확장성 있고 유지보수하기 좋다.
상속을 항상 사용하는 것이 좋을까?
당연히 아니다.
IS - A 관계(is a relationship; inheritance) 라는 용어가 있다.
IS - A 관계란 일반적인 개념과 구체적인 개념의 관계이다.
즉 '사람은 표유류이다' 와 같은 관계이다.
상속은 IS - A 관계에서 사용하는 것이 가장 효율적이다.
일반 클래스를 점차 구체화하는 상황에서 상속을 사용하는 것이다.
상속을 사용하면 많은 장점이 있지만, 하위 클래스가 상위 클래스형에 종속되기 때문에 이질적인 클래스 간에는 상속을 사용하지 않는 것이 좋다.
단순히 코드를 재사용할 목적으로 서로 관련이 없는 개념의 클래스들을 상속 관계로 사용하는 것은 좋지 않은 코드 작성법이다.
과목을 나타내는 Subject 클래스가 있다.
과목 아이디, 이름을 멤버 변수로 가지고 get(), set() 메서드도 제공한다.
이제 Student 클래스를 만들고자 한다.
모든 학생은 전공 과목(Subject)을 가지고 있다.
그러니 Subject 클래스에서 제공하는 여러 메서드를 활용하면 좋을 것 같다.
이런 경우에는 상속을 사용하지 않는 게 좋다.
왜냐하면 Subject가 Student를 포괄하는 개념의 클래스가 아니기 때문이다.
또한 Student 클래스를 상속받는 다른 클래스가 있을 수도 있다.
이런 경우에는 HAS - A 관계(has a relationship; association)로 표현한다.
HAS - A 관계란 한 클래스가 다른 클래스를 소유한 관계이다.
Subject는 Student에 포함되어 Student의 멤버 변수로 사용하는 것이 적절하다.
상속을 코드 재사용 개념으로 이해하면 안 되는 이유가 여기 있다.
재사용할 수 있는 코드가 있다고 해서 무조건 상속을 받는 것은 아니다.
상속을 사용하면 클래스 간의 결합도가 높아져서 상위 클래스의 변화가 하위 클래스에 미치는 영향이 크다.
따라서 상속은 '일반적인 클래스'와 '구체적인(확장되는) 클래스'의 관계에서 구현하는 것이 맞다.
여러 클래스를 한 번에 상속(다중상속)받을 수도 있나요?
자바 이전의 객체 지향 언어인 C++는 다중 상속을 지원했지만, 자바는 지원하지 않는다.
다중 상속으로 인한 모호성 때문이다.
예를 들어 두 개 이상의 상위 클래스에 같은 이름의 메서드가 정의되어 있다면, 다중 상속을 받는 하위 클래스는 어떤 메서드를 상속받을지 모호해진다.
객체 지향에서 다중 상속의 모호성에 대한 예가 다이아몬드 문제이다.
C++ 같은 언어에서는 문법적으로 이러한 문제를 해결하지만, 자바에서는 다중 상속의 장점보다는 모호함을 없애는 쪽을 선택해 다중 상속을 사용하지 않는 것이다.
따라서 extends 예약어 뒤에 오는 클래스는 반드시 한 개여야 한다.
'JAVA' 카테고리의 다른 글
| 추상 클래스 (0) | 2024.12.24 |
|---|---|
| 다운 캐스팅, instanceof (1) | 2024.12.23 |
| 다형성 (0) | 2024.12.20 |
| 메서드 오버라이딩, 가상 메서드 (1) | 2024.12.20 |
| 상속에서 클래스 생성과 형 변환, super() (0) | 2024.12.19 |