[그린컴퓨터] Server/JAVA(객체 지향 프로그래밍)

다형성 활용하기 { 배열과 다형성, 배열과 다형성 활용하기, 상속은 언제 사용할까 }

Ben의 프로그램 2023. 5. 31. 18:06
728x90

배열과 다형성?

  • 앞에서 배운 상속과 다형성을 활용하면 프로그램을 유지보수하는 데 매우 편리합니다. 
  • 이때 배열과 함께 사용하면 여러 하위 클래스 자료형을 상위 클래스 자료형으로 한꺼번에 관리할 수도 있습니다.

배열과 다형성 활용하기 (고객 분류에 GOLD 등급 추가 & 배열로 고객 구현)

  • 고객 등급 분류를 더 세분화 하기 위해 GOLD 등급을 추가하고자 한다고 생각해봅시다. 
  • GOLD 의 혜택은 다음과 같습니다. 
  • 할인율 10%, 보너스 포인트 2% 적립, 담당 전문 상담원은 없다.
  • 위 내용을 기반으로 Customer 클래스를 상속받아 GoldCustomer 클래스를 만들어보겠습니다.

  • Customer 클래스를 상속받는 계층은 위와 같습니다. 
  • 우선 polymorphism 패키지에 있는 Customer 클래스의 변수를 모두 protected 형으로 선언하여 witharraylist 패키지 내부에 있는 GoldCustomer 클래스가 사용할 수 있도록 만들었습니다.
package witharraylist;
import polymorphism.*;

public class GoldCustomer extends Customer{
	double saleRatio;

	public GoldCustomer() {
		super();
	}

	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명 구현하기 

  • 이제 여러 등급의 고객을 한 번에 관리할 수 있도록 구현해 보겠습니다. 
  • 예제 시나리오 : VIP 1명, GOLD 2명, SILVER 2명이 각각 10,000원짜리 상품을 구매했을 때 결과를 출력하세요.
  • 고객 인스턴스가 총 5개이므로 배열에 넣어서 관리하면 편리할겁니다. 
  • 객체 배열 ArrayList 는 자료형을 지정하여 선언해야 합니다. 
  • 우리가 사용할 클래스는 Customer, GoldCustomer, VIPCustomer 세 종류입니다. 
  • 배열의 자료형을 Customer 로 지정하면, VIPCustomer 와 GoldCustomer 클래스 모두 Customer 에서 상속받은 클래스이므로 이 배열에서 Customer, GoldCustomer, VIPCustomer 를 모두 사용할 수 있습니다. 
  • 이 배열에 Customer 하위 클래스의 인스턴스가 추가될 때는 모두 Customer 형으로 묵시적 형 변환이 발생합니다. 
package witharraylist;
import polymorphism.*;
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);
		
		for (Customer customer : customerList) {
			customer.showCustomerInfo();
		}
		
		int price=10000;
		for (Customer customer : customerList) {
			int cost = customer.calcPrice(price);
			System.out.println(customer.getCustomerName() + " 님이" + cost + "원 지불하셨습니다.");
		}
		
		for (Customer customer : customerList) {
			System.out.println(customer.showCustomerInfo());
		}
	}

}
  • 우선 Customer 클래스에서 customerName 변수는 protected 로 선언했습니다. 
  • 재미있는 것은 현재 배열에 있는 객체들이 Customer 클래스이거나 Customer 클래스를 상속받은 하위 클래스이더라도 protected 로 선언된 customerName 변수에는 접근할 수 없다는 것입니다. 
  • 그 이유는 어찌되었건 studentTest 클래스는 Customer 를 상속받지 않은 클래스이기 때문에 결국에는 customerName 변수에 접근할 권한이 없습니다. 
  • 따라서 지금 클래스에서 customerName 변수에 접근하기 위해서는 Customer 클래스에서 getter 메서드를 생성해주어야 합니다. 
  • 위의 코드를 한번 자세히 살펴보겠습니다. 
  • Customer 형으로 객체 배열 ArrayList 를 선언하였습니다. 
  • 그런 다음 Customer 클래스로 형 변환된 Customer, VIPCustomer, GoldCustomer 클래스의 인스턴스들을 ArrayList 배열에 추가했습니다. 
  • 22행에서는 향상된 for 문을 사용하여 배열에 추가된 인스턴스들의 showCustomerInfo( ) 메서드를 실행시켰습니다.
  • showCustomerInfo( ) 가 재정의된 클래스들은 인스턴스의 showCustomerInfor( ) 메서드가 실행됩니다. 
  • 27행에서 수행되는 calcPrice 도 마찬가지입니다. 
  • 중요한 것은 만약 재정의한 메서드가 가상 메서드 방식에 의해 자동으로 호출되지 않는다면 if-else if 문을 사용하여 각 자료형에 적합한 코드를 따로 구현해야 할 것입니다. 또한 새로운 등급의 고객이 추가로 필요한 경우에는 또 다른 조건을 구현해야 하므로 코드의 유지보수가 어려워집니다.  
  • 이런 경우에 상속과 다형성을 잘 활용하면 복잡한 코드를 간결하게 줄일 수 있고 확장성 있는 프로그램을 구현할 수 있습니다. 

상속은 언제 사용할까? (상속을 사용 안 할 때의 문제점으로 살펴보는 상속의 장점, 상속은 언제나 사용하는 것이 맞을까?)

  • 이전 예제에서 살펴보았던 VIP 고객 등급을 추가하는 문제를 생각해 보겠습니다. 
  • 이미 Customer 클래스가 구현되어 있는데 추가 요구 사항이 생긴 것입니다.
  • 간단하게 해결하는 방법은, Customer 클래스에 VIP 고객의 내용도 함께 구현하는 것입니다. 
  • 그런데 추가 기능을 이렇게 구현하면 Customer 클래스의 코드가 굉장히 복잡해집니다. 
  • 그 이유는 일반 등급 고객이 사용하지 않는 속성(상담원 ID, 할인율 등) 뿐만 아니라 VIP 고객만을 위한 서비스(기능) 까지 추가해야 하기 때문입니다. 
if (customerGrade == 'VIP') {
}
else if (customerGrade == "GOLD") {
}
else if (customerGrade == "SILVER") {
}
  • 위의 코드는 모든 등급의 내용을 넣었을 때 Customer 클래스의 모습을 보여주는 코드입니다.
  • 고객 등급에 따라 다르게 구현해야 하기 때문에 if-else if-else 문을 사용합니다. 
  • calcPrice( )  메서드 뿐만 아니라 여러 다른 메서드에서도 등급에 따른 구현이 필요하다면 클래스 전체에서 이러한 if-else if-else 문이 많이 사용될 겁니다. 
  • 이렇게 된다면 고객의 등급이 하나라도 추가되거나 삭제되면 유지보수가 매우 복잡해집니다. 
  • 앞에서 학습했듯이 상속을 사용하면 모든 등급에서 공통으로 상요하는 코드 부분은 상위 클래스인 Customer 클래스에 구현하고, 각 등급별 고객의 내용은 각각의 하위 클래스에 구현합니다.
  • 또한 새로운 등급의 고객이 추가되더라도 기존의 코드를 거의 수정하지 않고 새로운 클래스를 추가할 수 있습니다. 
  • 따라서 프로그램이 확장성 있고 유지보수하기 좋습니다.

상속을 항상 사용하는 것이 좋을까?

  • 당연히 그렇지 않습니다. 
  • 상속은 흔히 IS-A 관계일 때 사용하는 것이 가장 효율적이다라고 말합니다. 
  • IS-A 관계 라는 용어가 있습니다. (is a relationship; inheritance)
  • 'IS-A 관계' 란 일반적인 개념과 구체적인 개념의 관계입니다. 
  • 즉 '사람은 포유류이다'와 같은 관계입니다. 
  • 일반 클래스를 점차 구체화하는 상황에서 상속을 사용하는 것입니다. 
  • 상속을 사용하면 많은 장점이 있지만, 하위 클래스가 상위 클래스형에 종속되기 때문에 이질적인 클래스 간에는 상속을 사용하지 않는 것이 좋습니다. 
  • 단순히 코드를 재사용할 목적으로 서로 관련이 없는 개념의 클래스들을 상속 관계로 사용하는 것은 좋지 않은 코드 작성법입니다. 
  • 예시를 들어서 생각해보도록 하겠습니다. 
package inheritance2;

public class Subject {
	private int subjectID;
	private int subjectName;
	public int getSubjectID() {
		return subjectID;
	}
	public void setSubjectID(int subjectID) {
		this.subjectID = subjectID;
	}
	public int getSubjectName() {
		return subjectName;
	}
	public void setSubjectName(int subjectName) {
		this.subjectName = subjectName;
	}
	
	public void showSubjectInfo() {
		System.out.println(subjectID + "," + subjectName);
	}
}
  • 과목을 나타내는 Subject 클래스가 있습니다. 
  • 모든 학생은 전공 과목을 가지고 있습니다. 그러므로 Subject 클래스에서 제공하는 여러 메서드를 활용하면 좋을 것 같습니다.
  • 이런 경우 Student 클래스가 Subject 클래스를 상속받으면 되는 걸까요? 
  • 정답은 아닙니다. 
  • 왜냐하면 Subject 가 Student 를 포괄하는 개념의 클래스가 아니기 때문이고, 다른 말로 하면 IS-A 관계가 아닙니다. 
  • 게다가 Student 클래스를 상속받는 다른 클래스가 있을 수도 있습니다. 
  • Student 클래스는 Subject 클래스를 소유한 개념입니다. 
  • 이런 Student 와 Subject 의 관계를 'HAS-A 관계' 로 표현합니다. (has a relationship; association)
  • HAS-A 관계란 한 클래스가 다른 클래스를 소유한 관계입니다. 
  • HAS-A 관계를 가지는 Subject 클래스는 Student 클래스에 포함되어 Student 의 멤버 변수로 사용하는 것이 적절합니다. 
class Student {
	Subject majorSubject;
}
  • 상속을 코드 재사용 개념으로 이해하면 안 되는 이유가 여기에 있습니다. 
  • 재사용할 수 있는 코드가 있다고 해서 무조건 상속을 받는 것은 아닙니다. 
  • 상속을 하면 클래스 간의 결합도가 높아져서 상위 클래스의 변화가 하위 클래스에 미치는 영향이 크기 때문입니다.
  • 따라서 상속은 '일반적인 클래스'와 '구체적인 클래스'의 관계(IS-A 관계)에서 구현하는 것이 맞습니다. 

여러 클래스를 한 번에 상속 받을 수도 있을까?

  • 한 클래스가 여러 클래스를 상속 받는 것을 다중 상속이라고 합니다. 
  • 자바 이전의 객체 지향 언어인 C++는 다중 상속을 지원했지만, 자바는 다중 상속을 지원하지 않습니다. 
  • 여러 클래스에서 상속을 받으면 그만큼 다양한 기능을 상속받을 수 있는 장점이 있는데, 받지 않는 이유는 무엇일까요?
  • 그 이유는 다중 상속으로 인한 모호성 때문입니다. 
  • 예를 들어 두 개 이상의 상위 클래스에 같은 이름의 메서드가 정의 되어 있다면, 다중 상속을 받는 하위 클래스는 어떤 메서드를 상속받을지 모호해집니다. 
  • C++ 같은 언어에서는 문법적으로 이러한 문제를 해결하지만, 자바에서는 다중 상속의 장점보다는 모호함을 없애는 쪽을 선택해 다중 상속을 사용하지 않는 것입니다. 
  • 따라서 extends 예약어 뒤에 오는 클래스는 반드시 한 개여야 합니다.