다형성과 추상화

주8·2023년 1월 9일
0

다형성(Polymorphism)

  • 다형성이란 객체가 다양한 형태로 표현(실행)되는 것을 말한다.
  • poly(많은)와 morphs(다른 형태) 단어의 조합으로 하나의 action을 다양한 방식으로 수행할 수 있는 매커니즘이다.
    • 예) 상점에 있는 사람은 고객, 사무실에 있는 사람은 직원, 집에 있는 사람은 남편/아버지/아들, 파티에 있는 사람은 손님
    • ⇒ 같은 사람이라도 장소에 따라 다른 역할을 맡는다. 이를 다형성이라고 한다.
  • 각 하위 클래스들은 상위 클래스의 확장된 형태로 표현된다.
  • 다형성은 상속관계에서만 성립된다.
  • 하나의 참조 변수는 여러 타입의 객체들을 참조할 수 있다.
    • 상위 클래스 타입의 참조 변수가 하위 클래스 타입의 객체를 다룰 수 있는 것
  • 상위클래스인 Employee 객체의 레퍼런스 변수는 하위클래스의 CSR이나 Manager 객체의 레퍼런스를 참조할 수 있다는 것이다.
  • OOP에서의 다형성은 특정한 것이 다른 형태로 발생하는 상황, 컴퓨터 과학에서의 다형성은 동일한 인터페이스를 통해 서로 다른 유형의 객체에 액세스할 수 있는 개념이라고 설명하고 있다.

예제

상위클래스인 Employee type의 reference 변수(e1, e2)는 Manager와 CSR 즉, 하위클래스의 Reference를 저장할 수 있다.

public class Employee{
	//class_code_block
}

public class Manager extends Employee{
	//class_code_block
}

public class CSR extends Employee{
	//class_code_block
}

public class PolymorphicExample{
	public static void main(String[] args){
		Employee e1 = new Manager();
		Employee e2 = new CSR();
	}
}

동적 다형성(Dynamic Polymorphism)

재정의된 메서드에 대한 호출이 컴파일 타임이 아닌 런타임에 해결되는 프로세스 또는 메커니즘이다. 런타임 다형성(Runtime Polymorphism) 또는 동적 메서드 디스패티(Dynamic Method Dispatch)라고도 한다.


Java의 다형성의 유형

  • 정적 다형성(컴파일 타임 다형성)
  • 동적 다형성(런타임 다형성)

동적 다형성의 속성

  • 런타임에 실행할 메서드를 결정한다.
  • 동적 바인딩을 통해 처리될 수 있다.
  • 서로 다른 클래스 사이에서 발행한다.
  • 동적 다형성을 위해 하위 클래스 객체가 상위 클래스 객체에 할당되는 경우에 필요하다.
  • 상속관계에서 수행된다.

  • Mulilevel 상속의 경우 최하위 클래스가 상위클래스의 메서드 오버라이딩을 하지 않게 되면 상위클래스의 메서드가 수행된다.
class Animal{
	void eat(){System.out.println("먹기");}
}
class Dog extends Animal{
	void eat(){System.out.println("과일 먹기");}
}
class BabyDog extends Dog{
	void eat(){System.out.println("우유 마시기");}
	public static void main(String[] args){
		//재정의된 메서드가 호출된다.
		Animal a1, a2, a3;
		a1 = new Animal();
		a2 = new Dog();
		a3 = new BabyDog();
		a1.eat();
		a2.eat();
		a3.eat();
	}
}

[결과]
먹기
과일 먹기
우유 마시기

//Dog의 eat()메서드를 오버라이드하지 않았을 경우
public class BabyDogEx2 extends Dog{
	public static void main(String[] args){
		Animal a = new BabyDogEx2();
		a.eat();
	}
}

[결과]
과일 먹기

멤버 변수의 다형성

  • 런타임 다형성은 메서드 오버라이드를 통해 수행되며, 멤버 변수를 통해서는 수행될 수 없다.
  • 예제처럼 상위클래스 타입으로 객체 참조변수를 선언, 하위클래스 객체를 생성해서 할당해서 변수를 접근했을 때 변수는 재정의되지 않기 때문에 상위클래스의 멤버변수에 접근하게 된다.
class Bike{
	int speedlimit = 90;
}
class Honda extends Bike{
	int speedlimit = 150;

	public static void main(String[] args){
		Bike obj = new Honda();
		System.out.println(obj.speedlimit); //90
	}
}

다형성 배열

  • Homogenous Collections(균질적 모임): 동일한 클래스 타입을 배열에 저장
MyDate[] dates = new MyDate[2];
dates[0] = new MyDate(22,12,1964);
dates[1] = new MyDate(22,7,1964);
  • Heterogeneous Collections(이질적 모임)
    • 다른 클래스 타입들의 모음으로 아래 예제처럼 Employee란 상위클래스 타입으로 배열의 레퍼런스 변수를 선언하여 그 배열의 element에 동일한 타입이나 하위클래스 타입을 저장
    • 모든 클래스들은 Object class를 상속하고 있기 때문에 모든 종류의 element들을 array에 저장할 수 있다.
    • 단, primitive type(기본자료형)들은 Object class에 저장할 수 없다. 그렇기 때문에 각 primitive type에 대한 Wrapper class(Integer, Long 등)를 이용해서 저장하여야 한다.
Employee[] staff = new Employee[1024];
staff[0] = new Manager();
staff[1] = new Employee();
staff[2] = new Engineer();

Heterogeneous Collections Example

  • 상위클래스 Pet타입으로 배열을 선언하고 배열 element에 하위클래스 객체를 생성하여 할당한다.
  • for loop으로 배열의 각 인덱스에 접근하여 speak() 메서드를 호출하면 하위클래스 객체의 오버라이딩 메서드가 호출되어 다형성이 수행된다.
public clas Animal{
	public static void main(String[] args){
		Pet myPets[] = new Pet[4];
		myPets[0] = new Pet();
		myPets[1] = new Cat();
		myPets[2] = new Duck();
		myPets[3] = new Dog();

		for(int i=0; i<myPets.length; i++){
			myPets[i].speak();
		}
	}
}

[결과]
애완동물 말하기
야옹
꽥꽥
멍멍

Polymorphic Arguments

Argument type을 상위클래스 타입으로 선언하게 되면 하위클래스들의 타입으로 전달할 수 있다. (상속의 is a 관계 - Manager is an Employee)

//In the Employee class
public TaxRate findTaxRate(Employee e){}
//Meanwhile, elsewhere in the application class
Manager m = new Manager();
...
TaxRate t = findTaxRate(m);

instanceof Operator

  • instanceof는 A instanceof B 형태로 사용하며 A와 B가 서로 호환(상속관계)이 되는지 형변환 되는지 여부를 체크하는 연산자
  • instanceof는 인스턴스 타입을 비교하기 때문에 타입 비교 연산자라고도 한다.
  • true, false를 리턴하며, null값을 가진 변수에 instanceof 연산자를 사용하면 false를 리턴한다.
  • instanceof는 형변환 가능 여부를 체크하여 형변환 하기 위한 용도로 많이 사용한다.
  • 즉, argument를 통해 전달된 실제 객체를 판단하기 위한 용도이다.
  • 아래의 상속관계에서는 예제와 같이 instanceof 연산자를 활용할 수 있다.
public void doSomething(Employee e){
	if(e instanceof Manager){
		//Manager를 위한 처리
		System.out.println("Manager instance");
	}else if(e instanceof Engineer){
		//Engineer를 위한 처리
		System.out.println("Engineer instance");
	}else{
		//그 외의 처리
	}
}

public static void main(String[] args){
	InstanceOfExam i = new InstanceOfExamp();
	Employee emp = new Manager();
	Employee eng = new Engineer();
	i.doSomething(emp);
	i.doComething(eng);
}

[결과]
Manager instance
Engineer instance

instanceof 연산자로 다운캐스팅이 가능한지 확인 후 다운캐스팅을 수행하여 ClassCastException 예외를 방지한다.

class Animal{}

class Dog extends Animal{
	static void method(Animal a){
		if(a instanceof Dog){
			Dog d = (Dog)a; //downcasting
			System.out.println("다운캐스팅 가능");
		}
	}

	public static void main(String[] args){
		Animal a = new Dog();
		Dog.method(a);
	}
}

Casting object

  • 서로 상속관계에 있는 타입간의 형변환만 가능하다.
  • 하위타입 → 상위타입(Up-Casting): 형변환 생략 가능
  • 하위타입 ← 상위타입(Down-Casting): 형변환 생략 불가
  • 캐스팅을 통하여 해당 객체의 모든 멤버를 사용할 수 있다.
  • 다운 캐스팅은 하위클래스가 되어야 하고, 컴파일러가 이를 검사한다.
  • 객체의 타입은 Run-time에 체크된다.
Pet pet = new Pet();
Dog dog1 = new Dog();
Dog dog2 = null;

dog1.speak();
//하위클래스 -> 상위클래스로 업캐스팅(생략가능)
pet = dog1;
//상위클래스 -> 하위클래스로 다운캐스팅
dog2 = (Dog)pet;
dog2.speak();

추상화


Abstract class와 Abstract method

  • Java의 class design 중 상위클래스에서 메소드를 선언만 하고, 상속 받는 하위클래스에서 구체적인 구현을 하도록 유도하는 메소드를 abstract method라 부르며 이런 abstract method가 하나 이상 존재하는 class를 abstract class라고 한다. (미완성 클래스)
  • 다형성에서 보았던 Pet 상위클래스의 speak method의 경우를 보면 다음과 같다.
    • 모든 Pet들은 speak method가 필요하다.
    • Generic한 pet에서는 어떠한 pet에 의존하여 speak 행동을 할 수가 없다.
    • speak method를 abstract method로 선언하여 상속하는 모든 sub class에서 speak method를 각각의 행동에 맞게 재정의한다.
  • 만약 하위클래스에서도 speak method를 구현하지 않으면, speak method가 하위클래스에서 abstract method가 된다. 결국 그 하위클래스도 abstract class가 된다.

  • Abstract class는 일반 클래스와 달리 자신의 객체를 생성하지 못한다.
  • 생성자와 정적 메서드를 가질 수 있다.
  • Abstract class는 다형성을 표현할 목적으로 많이 사용하며, 프레임워크를 설계시에도 많이 사용된다.
  • Abstract method는 선언시에는 메소드 끝에 세미콜론(;)을 붙여야 한다. 즉, method body가 없다. 실제 구현을 하위클래스에서 해야만 한다.
  • Abstract는 직접 객체를 발생시킬 필요 없이, 상속에 의해 하위클래스에서 제어할 목적으로 사용될 수 있다.
  • Abstract class는 하위클래스에서 완전한 구현을 할 수 있도록 하려는 경우에만 abstract로 선언되어야 하며, 클래스의 인스턴스 생성을 막고자 하는 의도라면 private 생성자를 선언한 후 클래스 내부에서도 절대 호출하지 말고 다른 생성자도 선언하지 않도록 한다.
  • private 메소드를 abstract method로 선언하면 compile error가 발생한다.
  • static method, final method를 abstract method로 선언하면 compile error가 발생한다.
  • Syntax:
    abstract class 클래스명{
    abstract 리턴타입 메소드명(); ← 하나 이상의 추상 메소드 선언
    }

상속을 통해 다형성을 구현했던 Pet 예제를 abstract class와 method를 통해 구현

abstract public class Pet{
	public abstract void speak();
}

public class Dog extends Pet{
	@Override
	public void speak(){
		System.out.println("멍멍");
	}
}

public class Cat extends Pet{
	@Override
	public void speak(){
		System.out.println("야옹");
	}
}

public class Duck extends Pet{
	@Override
	public void speak(){
		System.out.println("꽥꽥");
	}
}

public class AbstractClassTest{
	public static void main(String[] args){
		Pet myPets[] = new Pet[4];
		myPets[0] = new Duck();
		myPets[1] = new Cat();
		myPets[2] = new Duck();
		myPets[3] = new Dog();

		for(int i=0; i<myPets.length; i++){
			myPets[i].speak();
		}
	}
}

[결과]
꽥꽥
야옹
꽥꽥
멍멍

Template method design pattern

  • Abstract class에서 template method로 기본 흐름(알고리즘)을 정의하고 하위클래스에서 구현한다.
  • 알고리즘의 구조를 유지하면서 하위클래스에서 처리 단계를 재정의 할 수 있다. Framework 개발시 많이 사용한다.
public abstract class AbstractDisplay{
	public abstract void open();
	public abstract void print();
	public abstract void close();
	//Template method - 알고리즘 구조(처리과정)
	public final void display(){
		open();
		for(int i=0; i<5; i++){
			print();
		}
		close();
		System.out.println();
	}
}

public class CharDisplay extends AbstractDisplay{
	private char ch;
	
	public CharDisplay(char ch){
		this.ch = ch;
	}
	@Override
	public abstract void open(){
		System.out.println("<<");
	}
	@Override
	public abstract void print(){
		System.out.println(ch);
	}
	@Override
	public abstract void close(){
		System.out.println(">>");
	}
	public static void main(String[] args){
		CharDisplay ch = new CharDisplay('@');
		ch.display();
	}
}

[결과]
<<@@@@@>>
profile
웹퍼블리셔의 백엔드 개발자 도전기

0개의 댓글