본문 바로가기
카테고리 없음

[JAVA] 다형성 활용하기

by Jayce_choi 2021. 7. 9.
반응형

이전 글에서 사용했던 것과 같이 상속과 다형성을 활용한다면 프로그램을 유지 보수하는데 매우 편리합니다.

그리고 이때 배열을 함께 사용함으로써 여러 하위 클래스 자료형을 상위 클래스 자료형으로 한꺼번에 관리가 가능합니다. 예제를 하나 해보겠습니다.

 

예제 : 일반 고객과 VIP 고객의 중간 등급 만들기

시나리오 : 고객이 증가하여 VIP 고객만큼 물건을 많이 구매하지는 않지만, 그래도 단골인 분들에게 혜택을 주기 위해서 GOLD 고객 등급을 부여하고 싶습니다. GOLD 고객의 혜택은 다음과 같습니다.

  • 제품을 살 때는 항상 10% 할인.
  • 2%의 보너스 포인트 적립
  • 담당 전문 상담원은 없음.

해당 시나리오를 만족하기 위해서 기존에 존재하던 VIP 클래스와 같이 새로운 고객 등급을 구현해야 합니다. 해당 등급은 VIPCustomer에서 제공해주는 리스트는 비슷할지언정 구체적인 수치는 다릅니다. 때문에 Customer 클래스를 상속을 받아서 GoldCustomer클래스를 새로 만들어야 합니다.

GOLDCustomer가 새로 생김으로써 고객 관리 시스템은 다음과 같이 확장됩니다.

먼저 골드 클래스 코드를 구현해보도록 하겠습니다.

GoldCustomer 클래스를 작성시 생각해야 할 요소는 VIP 클래스와는 다르게 상담원 아이디가 없으며 할인율과 관련한 수치들만 바뀌면 되므로 calcPrice메서드만 재 정의되면 됩니다.

* 밑에는 Customer(부모 클래스)와 VIPCustomer 클래스 코드가 있기에 실습을 하실 때 사용하시면 되겠습니다.

package witharraylist;

public class GoldCustomer extends Customer {
	double saleRatio; /* 할인율이 Customer 클래스에는 없으니 변수 추가. */
	public GoldCustomer(int customerID, String customerName) {
		super(customerID, customerName); /* super를 통해서 부모 클래스에 존재하는 변수의 값을 불러옴. ( 할인율이나 다른 수치들이 다를 뿐 이름과 ID는 부모 클래스에서 시작 됨 ) */ 
		customerGrade = "GOLD";
		bonusRatio = 0.02;
		saleRatio = 0.1; 
	}
	
	public int calcPrice(int price) {
		/* 유일하게 재정의 한 메서드 */
		bonusPoint += price * bonusRatio; /* 보너스 포인트는 부모 클래스에 있는 변수로서 고객 정보가 생성되고 결제를 진행할때마다 보너스를 쌓이게 하는 변수(VIP, GOLD 상관없이 다있는 변수 ) */
		return price -(int)(price * saleRatio);
	}
}

 

이전 코드 ( Customer )

package witharraylist;

public class Customer {
	protected int customerID;
	protected String customerName; 
	protected String customerGrade; 
	int bonusPoint;
	double bonusRatio;
	
	public Customer() {
		initCustomer();
	}
	
	public Customer(int customerID, String customerName) {
		this.customerID = customerID;
		this.customerName = customerName;
		initCustomer();
	}
	
	private void initCustomer() { // 생성자에서만 호출하는 메서드이므로 private으로 선언 ( 멤버 변수의 초기화 부분 ) 
		customerGrade = "SILVER";
		bonusRatio = 0.01;
	}
	
	public int calcPrice(int price) {
		bonusPoint += price * bonusRatio; 
		return price; 
	}
	
	public String showCustomerInfo() {
		return customerName + " 님의 등급은 " + customerGrade + "이며, 보너스 포인트는" + bonusPoint +"입니다.";
	}

	public int getCustomerID() {
		return customerID;
	}
	public void setCustomerID(int customerID) {
		this.customerID = customerID;
	}
	public String getCustomerName() {
		return customerName;
	}
	public void setCustomerName(String customerName) {
		this.customerName = customerName;
	}
	public String getCustomerGrade() {
		return customerGrade;
	}
	public void setCustomerGrade(String customerGrade) {
		this.customerGrade = customerGrade;
	}
	
}

 

이전 코드 ( VIPCustomer )

package witharraylist;

public class VIPCustomer extends Customer {
	private int agentID;
	double saleRatio;
	
	public VIPCustomer(int customerID, String customerName, int agentID) {
		super(customerID, customerName);
		customerGrade = "VIP";
		bonusRatio = 0.05;
		saleRatio = 0.1; 
		this.agentID = agentID;
	}
	public int calcPrice(int price) {
		bonusPoint += price * bonusRatio;
		return price - (int)(price * saleRatio);
	}
	public String showCustomerInfo() {
		return super.showCustomerInfo() + "담당 상담원 번호는 " +agentID + "입니다.";
	}
	public int getAgentID() {
		return agentID;
	}
}

이렇게 GoldCustomer까지 구현이 되었습니다. VIP 클래스를 구현했던거 처럼 Gold 클래스또한 비슷하게 작성되었습니다. 이제 배열 사용을 통해서 실습을 해보도록 하겠습니다. 

 

예제 2 : 배열로 고객 5명 구현하기

이제 배열을 이용하여 한번에 다양한 등급을 가진 고객 5명을 구현해봅시다.

예제 2 시나리오 : 이 회사의 고객은 현재 5명입니다. 5명 중에 VIP 고객은 1명, GOLD 2명, SILVER 2명으로 구성되어있습니다. 이 고객들이 각각 10,000원짜리 상품을 구매했을 때의 결과를 출력합니다.

해당 시나리오를 구현하기 위해서 고객 인스턴스가 총 5개 이기 때문에 배열에 넣어서 관리하면 편합니다. 때문에 ArrayList 자료형을 사용한 선언이 필요합니다.

사용할 클래스 : Customer, VIPCustomer, GoldCustomer ( 3가지 )

3가지의 클래스를 포함한 배열을 선언하기 위해서 우선 부모클래스인 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>();
		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 + "점입니다.");
		}
	}
}

객체 배열 ArrayList를 선언하였으며 14~18행까지 Customer 클래스와 하위 클래스 VIPCustomer, GoldCustomer의 인스턴스를 ArrayList에 추가하였습니다.

향상된 for문을 사용하여 고객 정보를 출력을 하였으며 이때 사용된 showCustomerInfo 메서드는 재정의 하지 않았으므로 Customer 클래스에 구현된 메서드가 호출될 것입니다.

그리고 각 인스턴스가 calcPrice() 메서드를 호출하면 현재 이 변수의 실제 인스턴스가 무엇인가에 따라서 재정의된 메서드를 각각 호출하여 계산하게 됩니다. 이것이 바로 다형성입니다.

 


추가 이야기

1. 상속은 언제 사용되어야 할까 ? 

VIP를 위한 클래스를 만들기 위해서 가장 간단하게 생각하면 이미 구현된 Customer를 이용하여 추가 내용만 더해주면 됩니다. 즉 Customer 클래스에 VIP를 위한 내용들을 추가하는 것입니다. 그러나 이 방법은 Customer 클래스의 코드가 많이 복잡해지는 단점이 존재하며 다음과 같은 문제가 발생합니다.

if ( CustomerGrade == "VIP")
 > 할인과 적립 많이

else if ( CustomerGrade == "GOLD")
> 할인과 적립은 적당히

else if ( CustomerGrade == "SILVER" )
> 적립만 수행

이렇게 각 조건문을 사용하여 복잡한 구분이 필요해지게 되며 이러한 구조는 하나의 등급이 사라지거나 추가될 경우 굉장히 코드의 유지 보수가 힘든 단점이 있습니다.

때문에 공통적으로 사용하는 부분에 대해서는 상위 클래스를 통해서 상속을 하여 간단하게 해결을 하여 기존의 코드를 거의 수정하지 않고 새로운 클래스를 추가할 수 있게 됩니다.

 

2. 그렇다면 상속을 항상 사용하는 게 좋은 건가?

해당 질문에 답을 하기 위해서 우선 IS-A 관계가 있습니다. IS-A(is a relationship; inheritance)라는 용어가 있는데 일반적은 개념과 구체적인 개념의 관계를 말합니다. 즉 상속을 사용할 때는 다음과 같은 관계로 구성되어 있을 때 사용하는 게 적절하며 이질적인 관계를 가진 클래스 사이에서는 상속을 사용하지 않는 게 좋습니다.

예를 하나 들어봅시다.

student라는 클래스가 있으며 과목과 관련된 메서드 등을 포함한 subject 클래스가 있습니다. 이 둘을 상속관계로 만들어야 할까요??

아닙니다. 왜냐하면 subject는 student를 포함하는 관계가 아니기 때문입니다.

상속은 이처럼 재사용 개념을 위해서 사용을 하는 게 아닙니다. 상속을 하게 되면 결합도가 높아지기에 상위 클래스가 하위 클래스에 미치는 영향력이 커지기 때문입니다. 때문에 상속을 사용하는 경우는 일반적인 클래스와 구체적인 클래스 사이에서 관계를 구축하는 것이 맞습니다.

 

출처 : Do it!, Introduction to JavaProgramming 

반응형

댓글