JAVA Basic) 다형성 활용하기
Programming/Java 기초

JAVA Basic) 다형성 활용하기

728x90

 

목차

     


     

    상속과 다형성을 활용하면 프로그램을 유지보수하는 데 굉장히 편리하다.
    여기에 배열까지 함께 사용하게 되면, 여러 하위 클래스 자료형을 상위 클래스 자료형으로 한꺼번에 관리할 수도 있게 된다.

    <GOLD 등급 만들기>

    새로운 요구 사항이 발생했다..!


    [예제 시나리오]
    고객이 늘어 VIP등급 보다 물건을 많이 구매하진 않지만, 그래도 혜택을 주는 등급(GOLD)을 추가하고 싶습니다.
    -제품을 살 때는 항상 10% 할인.
    -보너스 포인트 2% 적립
    -담당 전문 상담원 X


    새로운 고객 등급이 생겼다. 이 등급의 고객은 VIP고객과 보너스 포인트 적립률이 다르고, 담당 상담원이 없다.

    이 내용을 기반으로 Customer클래스를 상속받아 'GoldCustomer클래스'를 만들어보자.
    고객 관리 시스템은 다음과 같은 계층구조로 확장될 것이다.

    고객 관리 시스템의 계층 구조

    package witharraylist;
    
    public class GoldCustomer extends Customer {
    	double saleRatio;
    	
    	public GoldCustomer(int customerID, String customerName) {
    		super(customerID, customerName);
    		customerGrade = "GOLD";
    		bonusRatio = 0.02;
    		saleRatio = 0.1;
    	}
    
    	@Override
    	public int calcPrice(int price) {
    		bonusPoint += price * bonusRatio;
    		return price - (int)(price * saleRatio);
    	} //재정의한 메서드
    }

    GoldCustomer 클래스는 지불 가격과 보너스 포인트를 계산하는 calcPrice()메서드만 재정의했다. 이처럼 상속을 사용하면 새로운 기능이 추가되더라도 쉽게 구현이 가능해진다.

    배열로 고객 5명 만들기

    이제 여러 등급의 고객을 한번에 관리하는 기능을 구현하자.


    [예제 시나리오]
    고객은 현재 5명 : VIP = 1, Gold = 2, Silver = 2
    이 고객들이 각각 10,000원의 상품을 구매했을 때 결과를 출력.


    고객 인스턴스가 총 5개이므로 배열에 넣어서 관리하면 편할 것이다.
    객체 배열 ArrayList는 자료형을 지정하여 선언해야한다. 우리가 사용할 클래스는 Customer, GoldCustomer, VIPCustomer 세 종류. 배열의 자료형을 Customer로 지정하고, VIP와 Gold클래스 모두 Customer에 상속되어 있으므로 Customer형으로 선언한다. 이렇게 선언해야 모든 클래스를 사용할 수 있다.
    그리고 이 배열에 Customer 하위 클래스의 인스턴스가 추가될 때 모두 Customer형으로 묵시적 형 변환이 된다.

    ArrayList<Customer> customerList = new ArrayList<Customer>();

    테스트 프로그램을 구현해보자. (Customer, VIP, Gold 클래스는 생략)

    package witharraylist;
    
    import java.util.ArrayList;
    
    public class CustomerTest {
    	public static void main(String[] args) {
    		ArrayList<Customer> customerList = new ArrayList<Customer>();
    		
    		Customer customerLee = new Customer(10010, "이순신");
    		Customer customerShin = new Customer(10011, "신사임당");
    		Customer customerHong = new GoldCustomer(10012, "홍길동");
    		Customer customerYoul = new GoldCustomer(10013, "율곡");
    		Customer customerKim = new VIPCustomer(10014, "김유신", 1000);
    		
    		customerList.add(customerLee);
    		customerList.add(customerShin);
    		customerList.add(customerHong);
    		customerList.add(customerYoul);
    		customerList.add(customerKim);
    		//ArrayList의 add속성을 사용해 객체배열에 고객 추가.
    		
    		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.showCustomerInfo());
    		}
    	}
    }

    Customer형으로 객체 배열 ArrayList를 선언.
    Customer클래스와 하위 클래스의 인스턴스를 ArrayList에 추가한다.
    향상된 for문을 사용하여 고객 정보를 출력. 고객 정보를 출력하는 showCustomerInfo()메서드는 재정의하지 않았으므로 Customer클래스에서 구현된 메서드가 출력되었다.
    그리고 두 번째 향상된 for문으로 각 고객이 지불해야 할 금액과 적립된 보너스 포인트를 출력. 고객 등급에 따라 할인율과 적립금이 다르므로 calcPrice()메서드는 각 클래스에서 재정의되었다. 
    for(Customer customer : customerList)문장customerList배열 요소를 하나씩 가져와 Customer형 변수에 넣었다.
    고객 정보를 ArrayList배열에 저장할 때 Customer형으로 형 변환을 하여 추가했기 때문에 배열 요소를 가져올 때도 Customer형으로 가져오게 된다.
    그리고 각 인스턴스가 calcPrice()메서드를 호출하면 현재 이 변수의 실제 인스턴스에 따라 재정의 된 메서드를 각각 호출하여 계산한다.

    이것이 '다형성'이다.

    만약 재정의한 메서드가 가상 메서드 방식에 의해 자동으로 호출되지 않는다면 if-else if 문을 사용하여 각 자료형에 적합한 코드를 따로 구현해야 했을 것임. 그리고 코드의 유지보수가 굉장히 복잡하고 어려웠을 것이다.

    <상속은 언제 사용하는게 좋을까?>

    'IS-A 관계 (is a Relationship: inheritance)'라는 용어가 있다.
    IS-A 관계
    일반적인 개념과 구체적인 개념의 관계다. 즉 '사람은 표유류다'와 같은 관계.
    상속은 IS-A관계에서 사용하는 것이 가장 효율적이다. 일반 클랙스를 점차 구체화하는 상황에서 상속을 사용하는 것이다.
    상속을 사용하면 많은 장점이 있지만, 하위 클래스가 상위 클래스형에 종속되기 때문에 이질적인 클래스 간에는 상속을 사용하지 않는것이 좋다.

    단순히 코드를 재사용할 목적으로 서로 관련이 없는 개념의 클래스들을 상속관계로 사용하는 것은 좋지 못한 코드 작성법이다.

    다음 코드를 보자.

    package inheritance;
    
    public class Subject {
    	private int subjectID;
    	private int subjectName;
    	
    	public int getSubjectID() {
    		return subjectID;
    	}
    	
    	public void setSubjectID(int sujectID) {
    		this.subjectID = sujectID;
    	}
    	
    	public int getSubjectName() {
    		return subjectName;
    	}
    	
    	public void setSubjectName(int subjectName) {
    		this.subjectName = subjectName;
    	}
    	
    	public void showSubjectInfo() {
    		System.out.println(subjectID + ", " + subjectName);
    	}
    }

    이제 Student클래스를 만들려고 하는데, Subject클래스에서 제공하는 여러 메서드를 활용하고 싶다.
    이럴때 Student클래스가 Subject클래스를 상속받으면 되는걸까?

    이런 경우에는 상속을 하지 않는 것이 좋다.

    왜냐하면 Subject가 Student를 포괄하는 개념이 아니기 때문이다. 또한 Student클래스를 상속받는 다른 클래스가 있을 수도 있다.
    이런 경우에는 'HAS-A 관계(has a relationship: association)'로 표현한다.

    HAS-A 관계란 한 클래스가 다른 클래스를 소유한 관계이다.

    class Student{
    	Subject majorSubject;
    }

    Subject는 Student에 포함되어 Student의 멤버 변수로 사용하는 것이 적절하다.

    상속을 코드 재사용 개념으로 이해하면 안되는 이유가 이것이다.
    재사용할 수 있는 코드가 있다고 해서 무조건 상속을 받는 것이 아니다. 상속을 사용하면 클래스 간의 결합도가 높아져서 상위 클래스의 변화가 하위 클래스에 미치는 영향이 커진다.
    따라서 상속은 '일반적인 클래스'와 '구체적인(확장되는) 클래스'의 관계에서 구현하는 것이 맞다.


    [Do it! 자바 프로그래밍 입문] 도서로 공부하며 정리한 글입니다.

    300x250