(Day 24) OOP; 추상클래스, 추상메서드, 오버라이딩
복습
생성자에 대한 설명?
클래스를 객체로 생성할 때(사용할 때) 단 한번 실행되는 메서드가 생성자다. 왜 생성자가 필요한가? 객체가 유효한 상태로 존재하기 위해서는 기본적인 설정이 필요하다. 그 기본적인 설정을 하는 것이 생성자다. 만약 기본적인 설정을 할 게 아무것도 없다면, 아무것도 하지 않고 아무 정보도 받지 않는 생성자인 기본생성자를 쓰면 된다.
String 생성자
클래스와 객체지향
클래스는 데이터와 데이터를 다루는 연산자를 설계한 것이다.
공부에 대해서
물건을 볼 때도 위에서 볼 때 앞에서 볼 때 옆에서 볼 때 다 다르듯이 계속 다양한 시각에서 봐야 이해하게 된다.
인스턴스 메서드와 스태틱 메서드의 구분
싱글톤 패턴의 목적과 용도?
인스턴스를 딱 하나만 만들고 싶을 때. 생성자를 private으로 만들고, 그 생성자 실행을 public static method로 처리. 한번 생성하고 더 생성 안하게는 그 메서드에서 처리한다.
팩토리 메서드 패턴의 목적과 용도?
인스턴스의 생성 과정(설정 과정)이 복잡한 경우, 그것을 간단하게 해주는 메서드를 만들어서 사용하는 것을 말한다.
상속의 용도를 이해하고 구현하기
기존 코드를 손대지 않고 기능을 추가하려고 만든 문법이 상속이다. 추가나 복제후 변경은 유지보수가 어려워지는 문제가 있는데, 상속은 기존 코드를 그대로 두고 extends 키워드로 해당 코드의 변수와 메서드를 쓰겠다고 JVM에게 알려준다. (기존 코드를 복사해오는 것은 아니다)
상속은 다형적 변수를 가능하게 해준다.
다형적 변수는 무엇인가? 왜 쓰나?우리가 말을 할 때, 정확한 의미를 갖는 용어를 사용하기 보다는 그것을 포함하는 단어를 써서 말한다. 그것이 더 편하기 때문이다. 점심 먹었어? 라고 하지 짬뽕먹었어? 짜장먹었어? 이런식으로 물어보진 않잖아. 그게 프로그래밍에서 구현된 것이지.
C++ Bjarne Stroustrup
비야네 스트로스투룹이 C++ 를 만들었다. 그가 인터뷰에서 말한 것이, 다중 상속을 허용한 것을 후회한다고 했다. 다중 상속이 가진 문제점과 컴파일러의 복잡성 증가가 그 원인이라고 한다. C++ 이후에 나온 Java는 다중 상속을 허용하지 않는다.
상속의 두 가지 목적
상속의 두 가지 목적은 서로 반대되는 것 같이 보인다. Specialization과 Generalization 두 단어는 서로 반대되는 것처럼 보인다.
Specialization, 전문화
좀 더 전문화된 클래스를 만들고 싶은 것이다. 슈퍼 클래스의 메서드를 Overriding
, 즉 재정의
하여 전문화된 클래스를 만들고자 할 때 상속을 쓸 수 있다. 이 경우 상속은 일반적인 클래스에 기능을 추가하거나 재정의하면서, 각각의 서브클래스를 전문화하는 것이다.
Generalization, 일반화
전문화의 반대로, 일반화에 상속을 사용할 수도 있다. 차량으로 치면, 먼저 클래스들을 세단, 트럭과 같이 개별적인 객체를 나타낼 수 있게 설계한다. 그렇게 클래스들을 설계하고 나면, 코드 중복
문제가 발생한 것을 알 수 있다. 세단과 트럭은 유사성이 있는 차량이라는 범주 내에 있다보니, 데이터(모델명, 연비)나 연산자(가속하다, 감속하다) 같은 것들이 공통적으로 있는 것이다. 이런 공통되는 코드들을 뽑아서 차량이라는 슈퍼클래스를 만들어주면, 세단과 트럭에서 공통되는 부분을 차량이라는 하나의 클래스에서 공통으로 관리할 수 있게 되는 것이다.
정리하면
공통성이 있는 부분을 먼저 뽑아놓고, 각 세부 요소를 추가한 클래스를 만들어주면 그것이 Specialization이다.
각 요소들을 만들고 보았더니 공통점이 있어서, 그것들의 코드 중복을 해소하기 위해 슈퍼클래스를 만들어주면 그것은 Generalization이다.
이 두 가지가 상속의 목적이다.
추상 클래스, 추상 메서드
Generalization 을 목적으로 상속을 활용하여 슈퍼클래스를 설계한 경우, 이 클래스는 직접 사용하는 것을 제한하는 것이 나을 것이다. 그를 위해서 등장한 것이 추상 클래스이다. 추상 클래스는 코드 관리를 목적으로 만든 슈퍼클래스다.
리턴 데이터타입에 대해서
다형적 변수와 연계된 내용으로, 리턴 타입에도 다형적 변수 개념이 적용되어 그 리턴타입 및 리턴 타입의 서브클래스들 또한 리턴이 가능하다! 추상클래스와 연계해서 생각해보면, 리턴 타입이 클래스인 경우 아래와 같은 의미로 이해하면 된다.
- 추상클래스이므로 해당 타입으로 리턴될 일은 없다. (인스턴스를 못만드니까)
- 다형적 변수에 따라 그 추상클래스를 상속받아 만든 서브클래스들을 리턴타입으로 쓰겠다는 의미다.
추상 메서드에 대해서
공통 변수와 메서드를 뽑아서 슈퍼클래스를 만들었고, 이를 추상 클래스로 만들어서 공통 코드의 유지관리를 위해 사용하는 상황이다. 여기서 편리함에 대한 욕심(?)이 하나 더 추가된다. 공통으로 가지고 있는 메서드가 있어서 슈퍼클래스에 정의되어 있으나, 매번 재정의오버라이딩
해줘서 사용해야 하는 클래스가 있다. 공통되는 코드라는 것을 알려주는 점에서 슈퍼클래스에 정의를 해 줘야 하는 메서드다.
- 서브클래스들의 메서드 이름의 일관성이 필요해서
- 서브클래스들의 메서드 구현의 일관성이 필요해서
근데! 문제는 실수로 그 메서드를 재정의하지 않은 경우 슈퍼클래스의 공통 메서드를 실행하게 된다. 즉 재정의를 하지 않은 실수를 해도 나중에 알게 된다.
이건 재정의하지 않으면 실수야!
그래서 재정의를 필수로 해야 하는 메서드의 경우를 위한 문법이 발생했다. 그것이 추상 메서드 문법이다.
추상 메서드 문법은 서브클래스에서 재정의할 메서드를 나타내기 위한 문법이다. 서브클래스에서, 슈퍼클래스의 추상 메서드를 재정의하지 않으면 컴파일 오류가 발생한다. 개발자가 실수하지 않도록 재정의를 강제하는 것이다.
어떻게 쓰는가? 바디를 가지지 않은, 메서드 시그너처Method Signature
만 가진 메서드를 abstract
키워드를 붙여주면 된다. 추상 메서드는 이러한 목적으로 고안되었기에 추상클래스나 인터페이스에서만 선언될 수 있다. 다시 말하면, 추상메서드는 실행이 불가능한, 미구현 된 메서드 이기에 인스턴스로 구현하지 않는 추상클래스나 인터페이스에서만 가질 수 있는 메서드다.
1
2
3
4
5
6
abstract class AbstractClass {
...
abstract void abstractMethod();
//추상메서드는 바디가 없이 시그너처만 가진다.
...
}
콘크리트 클래스
비유를 위해 쓰는 말이 아니라 공식적인 용어다. 추상클래스가 아닌, 인스턴스를 만들 수 있는 클래스들을 콘크리트 클래스라고 부른다. 추상클래스의 반대말이다.
레퍼런스는 서브클래스만 가리킬 수있다.
슈퍼클래스는 항상 서브클래스보다 변수와 메서드가 적거나 같다. 그래서 슈퍼클래스에서는 서브클래스의 메서드나 변수를 쓸 수 없는 경우가 생긴다. 그래서 그걸 막아두었다. (컴파일러가)
오버로딩(overloading)?
파라미터가 다른, 같은 이름의 메소드를 사용하는 것이 오버로딩이다. 파라미터의 형식(타입과 개수)은 다르지만 같은 기능을 수행하는 메서드에 대해 같은 이름을 부여함으로써 프로그래밍의 일관성을 제공하기 위한 문법이다.
기술면접이 빡세지고 있다. 내가 뭘 배웠고 뭘 할 수 있는지를 정확히 말해야 서류합이 되고, 면접에서는 기술면접을 봐야 하니 용어와 개념, 목적에 대해서 잘 말할 수 있어야 한다.
오버라이딩(Overriding)?
오버라이딩은 서브클래스가 상속받은 메서드를 재정의하는 것을 말한다. 서브클래스는 변수가 추가되는 등 슈퍼클래스랑 다른 점이 생기기 때문에, 상속받은 메서드를 변경하여 쓰고 싶은 욕구가 생긴다. 이것을 매번 새로운 이름의 메서드로 선언해야 한다면, 개발자들이 슈퍼클래스, 서브클래스 각각의 메서드들이 무엇이 잇는지 암기해야 할 것이다. 그래서 오버라이딩이라고, 서브클래스에서 상속받은 메서드를 재정의하는 기능을 만들었다.
부모 클래스로부터 상속 받은 메서드를 재정의할 수 없어서 새 메서드를 만들어야 한다면, 같은(또는 유사한) 일을 하는 메서드에 대해 안타깝게도 다른 이름으로 메서드를 만들어야 하기 때문에 개발자는 여러 개의 메서드를 암기해야 하는 번거로움이 생긴다. => 이런 문제를 해결하기 위해 등장한 문법이 “오버라이딩(overriding)”이다!
필드 오버라이딩
요약 : 쓰지마세요.
슈퍼클래스와 서브클래스는 같은 이름의 변수를 가질 수 있다. (파라미터의 순서나 타입이 달라야하는, 메서드의 오버로딩처럼 변수의타입은 달라야 한다.)
이런 경우에, 해당 변수를 참조하는 경우, 레퍼런스가 선언될 때 타입으로 사용된 클래스에서 먼저 찾는다. 슈퍼클래스의 변수를 덮어 써 버린 것이다. (Overriding)
슈퍼클래스의 필드를 감춰야 할 이유가 있을까? 메서드는 유사기능을 재정의해서 써야 하는 경우가 많지만 필드는 그렇지 않다. 혼동을 가하기 쉽다.
그리고 복잡하다. this 사용하는 경우 레퍼런스의 데이터타입을 말하는 클래스를 따라간다. super 사용하는 경우 슈퍼클래스의 필드를 먼저 찾는다.
오버라이딩 실수가 발생할 수 있기에..
애노테이션 주석에서 컴파일러에게 검사를 요청할 수 있다 @Overrride 라고 컴파일러에게 알려주면 된다. 그러면 컴파일러가 오버라이딩해야 한다고 전제하고 문법 검사를 한다.