2022-01-14 다향성 / 추상클래스 / 인터페이스

GGAE99·2022년 1월 14일
0

진도

목록 보기
7/43

오늘은 자바의 다향성, 추상클래스, 그리고 인터페이스에 대해 공부했다. 이게 어제 공부했던 클래스의 상속에서 더 들어가는 내용이다. 상속의 발전이냐하면... 그거는 아닌거같고 그냥 기능이라고 생각하겠다.
난 말을 참 못한다.

다형성

다형성은 여러개의 형태를 갖는다는 의미로, 객채지향 프로그래밍의 3대 특징 중 하나이다.
저번에 객체지향 프로그래밍으 3대 특징에대해 서술했는데, 다시 한번 적어보자면 다음과 같다.

  1. 캡슐화
  2. 상속
  3. 다형성

이러면 필자는 이제 객체지향 프로그래밍의 3대 특징에 대해 공부를 했다고 말할 수는 있겠다. 와!
근데 잘하는건 아니니까 더 열심히 코드를 짜보고, 내걸 만들어야겠다.

각설하구 다형성이라는 것은 상속을 이용한 기술로, 자식객체를 부모클래스 타입의 변수로 다룰 수 있는 기술이다. 무슨 뜻인지는 찬찬히 정리해보자. 나도 뭔소린지 잘 몰랐다.

예를들어 Monitor라는 클래스가 있다고 가정해보자 그리고 그 클래스 안에는 모니터의 성질들이 들어있다고 가정해보자.

public class Monitor{
	private boolean power;
	private int volume;
    	private int size;
}

그러면 이 Monitor라는 클래스를 상속해 모니터의 기본 성질들을 가져오고, 상속해온 자식 클래스에서는 그 클래스만의 성질을 더 추가할 수 있을 것 이다.

public class LGMonitor extends Monitor{
	public String gajuenEnLG;
}
-------------------------------------------
public class SamsumgMonitor extends Monitor{
	public String hajimanSamsungGgirlSuuji;
}

이렇게 생성한 클래스를 이용해서 타입은 Monitor의 타입이지만, 객체는 다른 클래스에서 생성하는 것이 가능하다.

Monitor LGM = new LGMonitor();
Monitor SM = new SamsungMonitor();

부모타입의 변수에 자식타입의 객체를 저장하는 것 이다.
하지만 반대의 경우로 자식타입의 변수가 부모타입객체를 저장할 수 없다.

LGMonitor LGM = new Monitor(); //이게 안된다.

클래스 형 변환 - up casting

지금 위에서 한 객체생성 과정이 up casting이다. 솔직히 이부분은 내가 이해한게 제대로됐는지 확신이 안선다... 틀렸으면 나중에 수정해야겠다.

public class Monitor{
	private boolean power;
	private int volume;
    	private int size;
}
public class LGMonitor extends Monitor{
	public String gajuenEnLG;
}

Monitor LGM = new LGMonitor();

이와 같은 코드의 경우, LGM 객체는 LGMonitor의 객체로 생성되었지만, 타입은 Monitor타입이기 때문에, LGMonitor의 문자열 gaJuenEnLG를 사용할 수 없으며, Monitor객체에 있는 변수와 메소드만 사용이 가능하다.

클래스 형 변환 - down casting

up casting의 경우에는 참조한 객체에만 있는 변수와 메소드를 사용할 수 없었다.
그러나 down casting으로 형변환을 시켜준다면 참조한 객체의 변수와 메소드를 사용할 수 있다.

Monitor LGM = new LGMonitor();
((LGMonitor)LGM).gajuEnenLG;

down casting을 이용해 객체를 불러내어 자료를 사용하는 것이 가능하다.
클래스간의 형 변환은 반드시 상속관계에 있는 클래스끼리만 가능하다.

추상 클래스

추상클래스는 뜻한다. (요거를)

  • 몸체가 없는 메소드(추상 메소드)를 포함한 클래스
  • abstract 키워드가 선언부에 달린 클래스
public abstract class Main{
	private int num;
    	private String str;
        public abstract void chu();
}

코드를 보면 Main클래스 안에있는 chu메소드의 내용이 비어있다. 이는 추상 클래스를 상속한 때 오버 라이딩(상속해서 가져갈때 메소드를 수정해서 사용하는 것)을 강제하는데 그 목적이 있다.

public class test extends Main{
}

위와 같은 코드를 작성하면 Main클래스가 덜 완성된 추상 클래스이기 때문에 오류가 난다.

public class test extends Main{
	@Override
    	public void chu(){
		System.out.println("오버 라이드")    
    	}
}

오버 라이드를 통해 완성된 코드로 바꿔주면, 클래스가 정상적으로 작동될 것 이다.

추상 클래스의 특징을 적어보고 넘어가보자.

  1. 미완성 클래스(abstract 키워드 사용)로 자체적으로 객체 생성이 불가능하다.
    -> 반드시 상속하여 객체를 생성한다.
  2. abstract 메소드가 포함된 클래스는 반드시 abstract 클래스이다.
    그러나 abstract메소드가 없어도 abstract 클래스를 선언하는 것은 가능하다.(객체 생성 방지)
  3. abstract 메소드 외에 일반 메소드와 변수를 포함할 수 있다.
  4. 객체 생성은 안되나, 참조형 변수 type으로는 사용이 가능하다.(다형성으로는 가능하다.)

인터페이스

상수와 abstract메소드만으로 이루어진 추상클래스의 변형체이다. 메소드의 통일성을 부여하기 위해서 abstract 메소드만 따로 모아 놓은 것으로, 상속 시 인터페이스에 정의된 모든 메소드를 구현해야 한다. 그러니까 아까 본 추상 클래스가 추상 메소드와, 일반 정의된 메소드, 변수 등이 섞여있다고 하면, 인터페이스는 모든 메소드가 추상 메소드인 것 이다.

[접근제어지시자] interface 인터페이스명{
}
public interface Monitor{
public static final boolean power=true;//변수는 상수형변수만 가능
public void powerOn();
//모든 메소드가 abstract메소드이므로 abstract생략이 가능
}

인터페이스를 사용하는 방법이었다.
인터페이스의 특징과 장점을 정리해보자.

인터페이스의 특징

  • 인터페이스의 모든 메소드는 묵시적으로 public이고 abstract이다.
  • 변수는 public static final이다.
  • 객체 생성은 안되나, 참조형 변수로서 가능하다.(다형성으로는 가능)

인터페이스의 장점

  1. 상위 타입의 역할로 다형성을 지원하여 연결해주는 역할을 수행 가능하다.
  2. 해당 객제가 다양한 기능을 제공한다해도, 인터페이스에만 해당하는 기능만을 사용할 수 있도록 제어하는것이 가능하다.
  3. 공통 기능상의 일관성을 제공하고, 공동작업을 위한 가이드라인이 제공된다.

인터페이스를 사용하는 이유를 계산기 툴을 만드는 작업을 예로 들어보겠다.

public interface CalcInterface {
	//더하기
	public int add(int num1,int num2, int num3);
	//빼기
	public int minus(int num1, int num2);
	//곱하기
	public int multiple(int num1, int num2,int num3);
	//나누기
	public double div(int num1, int num2);
}

이것이 인터페이스다. 위의 코드를 보면, add, minus, multiple, div의 네가지 메소드를 만들려는 것을 확인할 수 있다. 그리고 이 메소드의 알고리즘을 구현하는 사람이 A, 실제 실행 하는 메인 메소드를 구현하는 사람 B가 있다고 해보자.

public class CalcImpl implements CalcInterface{
	
	
	@Override
	public int add(int num1, int num2, int num3) {
		return (num1+num2+num3);
	}

	@Override
	public int minus(int num1, int num2) {
		return (num1-num2);
	}

	@Override
	public int multiple(int num1, int num2, int num3) {
		return (num1*num2*num3);
	}

	@Override
	public double div(int num1, int num2) {
		return ((double)num1/num2);
	}

이것이 계산기 인터페이스를 받고 계산해주는 알고리즘을 짠 모습이다. 굉장히 간단하게 매개변수들을 더하고, 빼고, 곱하고, 나눴다. 지금은 굉장히 쉬운 예를 들었지만, 이것이 구현하는데 시간이 걸린다고 생각해보자. B는 A가 만든 알고리즘을 사용해 실행파일을 만들 것 이다. 그런데 A가 본인의 일을 하는동안, B는 아무것도 못하게된다. B가 써야할 것을 A가 만들고있으니까. 그래서 인터페이스가 필요한 것 이다. B 또한 인터페이스를 받았다.

public class CalcMain {
	private Scanner sc;
	private CalcImplTest calc;
	public CalcMain() {
		super();
		sc = new Scanner(System.in);
		calc  = new CalcImplTest();
	}
	public void main() {
		System.out.println("===== 계산기 =====");
		System.out.println("1. 더하기");
		System.out.println("2. 빼기");
		System.out.println("3. 곱하기");
		System.out.println("4. 나누기");
		System.out.println("0. 종료");
		System.out.print("선택 > ");
		int sel = sc.nextInt();
		switch(sel) {
		case 1:
			break;//case 1 done
		case 2:
			break;//case 2 done
		case 3:
			break;//case 3 done
		case 4:
			break;//case 4 done
		case 0:
			return;//case 0 done
		default:
			break;//default done
		}//switch end
	}//main end

그리고 B는 계산기를 돌아가게 하는 메인메뉴를 만들었다. 이제 B는 더하기 실행 메소드부터 만들어나가기위해 인터페이스를 참고했고, 더하기 메소드에 들어갈 숫자가 몇개인지 매개변수의 갯수를 통해 알 수 있었다. 이를 통해 B는 A가 잘 구현해줄 것을 믿고 임시로 메소드 Test클래스를 만들어 실행하는 클래스를 만들어가기 시작했다.

public class CalcImplTest implements CalcInterface{

	@Override
	public int add(int num1, int num2, int num3) {		
		return -1;
	}

	@Override
	public int minus(int num1, int num2) {		
		return -2;
	}

	@Override
	public int multiple(int num1, int num2, int num3) {		
		return -3;
	}

	@Override
	public double div(int num1, int num2) {
		return -1.0;
	}
}

return타입만 맞춰서 임의의 값을 넣은 후, 실행 클래스를 만들고, 제대로 굴러가는지 확인했다.

public void add() {
		System.out.print("첫번째 숫자 입력 : ");
		int num1 = sc.nextInt();
		System.out.print("두번째 숫자 입력 : ");
		int num2 = sc.nextInt();
		System.out.print("세번째 숫자 입력 : ");
		int num3 = sc.nextInt();
		int result = calc.add(num1, num2, num3);
		System.out.println("결과 : "+result);		
	}//add end
	
	public void minus() {
		System.out.print("첫번째 숫자 입력 : ");
		int num1 = sc.nextInt();
		System.out.print("두번째 숫자 입력 : ");
		int num2 = sc.nextInt();
		int result = calc.minus(num1, num2);
		System.out.println("결과 : "+result);
	}//minus end

B는 A가 만들어줄 메소드의 이름과, 들어갈 수의 갯수 또한 인터페이스를 통해 알 수 있었기 때문에, 미리 실행할 파일을 만들어놓울 수 있었다.
그리고 A의 작업이 끝나면, 임의의 값을 넣어놓았던 test메소드를 A가 만든 메소드로 교체하면 되는 것 이다. 이렇게 공동 작업을 진행할 때, 가이드라인을 잡아놓고, 다시 코드를 짜는일이 없도록 해주는게 이 인터페이스라는 것이 하는 역할이다.

인터페이스를 사용하는 이유까지 정리해봤다. 오늘은 여기까지 하겠다! 안녕!

잘못된 정보가 나와있거나, 공유하고싶은 정보가 있으시다면 언제나 댓글 환영입니다!

0개의 댓글