안녕하세요. 린내입니다~!
오늘은 추상클래스에 대해 알아보려고 합니다.
추상클래스는 그 명칭에서 느껴지듯이.. 실무에서도 상당히 추상적으로 사용되는 느낌이 강한듯한데요.
알듯 말듯한 추상클래스에 대해서 이번 기회에 제대로 정리해보겠습니다.
하지만 사실, 오늘의 주인공은 추상클래스가 아닐수도 있습니다.
이 글의 발단은 아래의 게시글에서 시작되었는데요.
https://stackoverflow.com/questions/3340032/are-utility-classes-evil
Are utility classes evil?
I saw this question: If a "Utilities" class is evil, where do I put my generic code? And I thought, why are utility classes evil? Let’s say I have a domain model that is dozens of classes...
stackoverflow.com
유틸리티 클래스로 사용되는 정적 클래스가 악마..? 라는 무시무시한 속설에 의해 시작되었습니다.
간단하게 말해서, 정적 클래스는 전혀 객체지향적이지 않고 절차지향적이다. 그래서 단점이 매우 많다. 그래서 악마다.. 라는 주장이 개발자들 사이에서 계속 지속되고 있는 것 같아 보였습니다.
추상클래스에 대해 알아보자면서 왜 갑자기 정적 유틸리티 클래스에 대해 말하는지 궁금하시는 분들이 계실거예요.
요근래 스프링에 추가된 유틸리티 클래스는 단순한 정적 클래스로 선언되는 것보다 “추상클래스“로 선언되는 경우가 많고, 기존의 정적 클래스로 추상클래스로 리팩토링하는 경우도 종종 있기 때문입니다.

위의 클래스는 final로 선언된 정적 유틸클래스를 추상 클래스로 리팩토링 한 경우입니다.
이 외에도 PatternMatchUtils, ReflectionUtils, FileCopyUtils, SystemPropertyUtils, StreamUtils 등등의 많은 Utils 클래스가 추상클래스로 선언되어 있음을 알 수가 있습니다.
“추상 클래스는 유틸리티 클래스의 타입으로 주로 쓰인다.”
라는 문구는 공부나 실무를 해오면서 한 번도 들어본 적이 없는 말인데, 실제로는 이런식으로 작성이 왕왕되고 있는 것 같았습니다.
그러면 이제부터, 추상클래스가 정확히 무엇이고 왜 악마라고 까지 불리는 정적 클래스를 대체해 유틸리티성 클래스를 추상클래스로 선언하고 있는지까지 한 번 같이 알아보시죠!
추상클래스의 존재는 상속을 목적으로 한다
여러분은 “추상클래스“하면 어떤 단어가 가장 먼저 떠오르시나요?
저의 경우엔 역시 “상속“이란 단어가 먼저 떠오릅니다.
그도 그럴 것이, 저희가 자바를 처음 배울 때 추상클래스는 항상 상속 및 다형성 관련 챕터에서 볼 수 있었기 때문인데요.
언제나 “인터페이스“에 대한 많은 설명 다음에 추상클래스는 비교적 간략하게 설명되어 있었던 것 같습니다.
그도 그럴것이 인터페이스는 자바, 특히 스프링의 의존성 주입과 더해지면 매우 강력한 기능이 되죠.
(추상클래스도 DI는 가능합니다)
하지만 둘 사이에 비슷한 점이 많아서 항상 헷갈리곤 하는데요..
인터페이스와 추상클래스에서 대해 아주 간략하게 설명을 드릴게요 (이때까짐나 해도 간략하려고 했는데....)
추상화의 정도 - 인터페이스 VS 추상클래스
- 인터페이스
- 다중 상속 가능
- 기능(행위)에 대한 정의 → 기능(행위)에 대해 관심
- 추상클래스
- 다중 상속 불가
- 객체(존재)에 대한 정의 → 객체(존재)에 대해 관심
인터페이스와 추상클래스에는 여러 차이점들이 있지만 무엇보다 가장 큰 기능적인 차이점은 “다중 상속“이 가능하냐, 불가능하냐일 것입니다.
이 차이점은 어디서 오는 것일까요?
다중 상속의 가능 유무는 바로 인터페이스와 추상클래스의 관심이 다르다는 것에 있습니다.
인터페이스는 기능(행위)에 관심이 있고, 추상클래스는 객체(존재)에 관심이 있습니다.
그리고 “기능”에 대한 관점에서 “다중상속”은 객체지향원칙 중 하나인 “단일책임원칙”을 해치지 않는 반면,
객체(존재)에 대한 관점에서 “다중상속“은 “단일책임원칙”을 해치게됩니다.
- 인터페이스 - 행위에 관심 - 다중상속 → 단일책임원칙을 벗어나지 않는다!
- 추상클래스 - 존재에 관심 - 다중상속 → 단일책임원칙을 벗어난다!
이렇게 정리해볼 수 있겠습니다.. 쉽게 와닿지 않으신가요?
그럼 좀 더 이해가 쉽게 예시 코드를 보실까요?

패키지는 이렇게 구성해보았습니다.
각 행위들을 정의한 interface 패키지
각 존재의 상위 분류를 정의한 abtracts 패키지
각 단일 존재를 정의한 clazz 패키지가 있습니다.
- 인터페이스 >>> 추상 클래스 > 클래스
위의 순서대로 추상화 정도를 표현해 볼 수 있습니다.
추상 클래스의 명칭에 “추상”이라는 단어가 들어가 있어서 언뜻 추상의 정도가 클 것이라고 오해할 수도 있지만
인터페이스가 추상클래스보다 추상화 정도가 훨씬 큽니다.
반면, 추상클래스는 일반 클래스보다 약간의 추상화가 되있다고 볼 수 있습니다.
각각의 소스코드를 보실까요?
public interface Breathing { //숨쉬기
// 생략되어 있을 뿐 디폴트 메소드를 제외한 인터페이스의 메소드는 추상 메소드입니다.
abstract void respire(); //호흡하다
}
public interface Diving { //잠수하기
void dive(); // 잠수하다
}
public interface BreathingUnderwater extends Breathing, Diving { //수중호흡하기
void openGills(); // 아가미를 벌린다..
}
public interface Eating { //먹기
void bite(); //씹다
void digest(); //소화하다
}
public interface Running { //달리기
//모든 인스턴스의 멤버는 상수입니다. (static final) 생략가능
//달리는 속도는 음수가 될 수 없다.
static final boolean IS_SPEED_MINUS = false;
//광속을 초과할 순 없다
int MAX_SPEED = 299792458;
void bendKnees(); //무릎을 굽히다.
void jump(); //점프하다.
}
- 위처럼 인터페이스는 각각의 기능 정의서라고 볼 수 있습니다.
- 추상화가 목적이기에 기능 정의 의외의 기능 상세(구현 메소드)나 대상의 상태(인스턴스 변수)는 원칙적으로는 갖고 있을 순 없습니다.
- 자바8 이상 디폴트 메소드, 정적메소드 선언이 가능해졌지만 호환성을 위한 목적으로 인터페이스의 주요 기능이 될 순 없습니다.
- https://stackoverflow.com/questions/25784417/why-we-need-default-methods-in-java
Why we need default methods in Java?
I'm taking a look to Java 8 news compared to 7 and in addition to very interesting things like lambdas or the new time framework, i found that a new feature(?) was introduced: default methods. I f...
stackoverflow.com
그럼 이를 사용하는 추상 클래스들을 보겠습니다.
// 어류
abstract public class Fish implements BreathingUnderwater, Eating { //다중상속
boolean isSeeFish;
// 멤버 변수를 갖을 수 있으나 생성자를 직접사용하지 못하고 상속을 통한 일반 클래스에서 초기화 가능
// -> 상속을 통하여 상태를 갖을 수 있다.
public Fish(boolean isSeeFish) {
this.isSeeFish = isSeeFish;
}
// 어류는 추상클래스에서 구현메소드 작성해보지 않겠습니다..
}
// 포유류
abstract public class Mammal implements Breathing, Eating, Running { //다중상속
void breastfeed(){
System.out.println("쪽쪽");
// 젖을 먹이다.. (공통)
}
// 인터페이스 보다는 추상화의 정도가 작지요..?
public abstract void bark(); // 짖는 방법은 다 다릅니다..
// 포유류는 인터페이스의 추상 메소드를 추상클래스 다 구현한다고 해볼까요?
@Override
public void respire() {}
@Override
public void bite() {}
@Override
public void digest() {}
@Override
public void bendKnees() {}
@Override
public void jump() {}
}
위에서 볼 수 있듯 추상클래스를 통해 인터페이스에서 정의 했던 기능들의 사용처를 묶어주고 추상화 정도를 낮춰주었습니다.
두 추상 클래스는 어류와 포유류라는 추상화된 존재를 정의하고 있는데, 어떻게 보이시나요?
각각 인터페이스를 다중 상속을 하고 있음에도 불구하고 단일책임원칙을 헤치지 않는 것을 느낄 수 있으신가요?
인터페이스는 존재가 아닌, 존재에 대한 행위를 정의한 것이기 때문입니다.
그럼 이제 일반 클래스 레벨로 추상화 레벨을 낮춰보겠습니다.
public class GoldenFish extends Fish {
//추상클래스가 상태를 갖으려면 상속을 통해 인스턴스가 되어야함!
public GoldenFish(boolean isSeeFish) {
super(isSeeFish);
}
// 추상 클래스에서 추상메소드 구현을 하지 않았기 때문에 일반 클래스에서 구현 필요
@Override
public void respire() {}
@Override
public void openGills() {}
@Override
public void dive() {}
@Override
public void bite() {}
@Override
public void digest() {}
}
public class Dog extends Mammal {
public public String color;
public Dog(String color) {
this.color = color;
}
// 나머지 추상 메소드들은 추상메소드에서 구현했기 때문에 그냥 상속을 받아 사용 가능
@Override
public void bark() {
System.out.println("멍멍");
}
}
클래스에서는 이제 추상화 레벨이 많이 낮아 졌습니다.
추상클래스에서도 구현을 해놓지 않은 추상 메소드도 클래스 레벨에서는 다 구현을 해주어야합니다.
이제 충분히 추상적이지 않고 구체적 대상으로 존재 가능하니, 구체화를 해준다고 생각하면 될 것 같습니다..
그 다음으로, 추상화 레벨의 제일 아래, 제일 구체적인 대상에 대한 표현을 해보자면 어떻게 할 수 있을까요?
public void example(){
// 추상클래스의 멤버변수는 상속을 통해 인스턴스화 되어야 초기화 가능함 -> 추상클래스 자체로 상태를 갖을 수 없다!
GoldenFish myGoldenFish = new GoldenFish(false);
// Dog는 객체, myDog은 인스턴스
Dog myDog = new Dog("brown");
Dog yourDog = new Dog("white");
}
자바로 표현할 수 있는 가장 구체적인 레벨은 바로 “인스턴스”입니다.
“우리집 강아지는 갈색 털을 갖고 있다.”
이제 정말로 단일한, 구체적인 존재에 대한 표현까지 해볼 수 있게 되었습니다.
단일한 존재인 인스턴스가 되면 위에서 보시듯 당연히 컬러라는 상태를 갖을 수 있게 됩니다.
추상클래스는 그 자체로 상태를 갖을 순 없지만,
일반클래스로 상속을 통하여 인스턴스가 될 수 있고 상태를 갖게될 수 있습니다.
“내 금붕어는 바다고기가 아니다“
추상클래스에서 정의했던 상태를 갖을 수 있게 되었지만
위에서 표현했던 클래스의 상태보다는 조금 더 넓은 범위의 추상화라는 것을 알 수 있죠?
그렇다면 위 인터페이스 예시 중 이건 어떻게 이해해볼 수 있을까요?
public interface Running { //달리기
//모든 인스턴스의 멤버는 상수입니다. (static final) 생략가능
//달리는 속도는 음수가 될 수 없다.
static final boolean IS_SPEED_MINUS = false;
//광속을 초과할 순 없다
int MAX_SPEED = 299792458;
void bendKnees(); //무릎을 굽히다.
void jump(); //점프하다.
}
인스턴스는 일반 멤버 변수를 갖을 수 없고, 불변 상수만 갖을 수 있습니다.
그런데 이는 당연한 것입니다.
뛰기라는 기능 정의가 순간적인 존재의 상태를 갖고 있을 수 없습니다.
다만 정적인 정의 혹은 정보만 갖고 있을 수 있을 뿐입니다.
달리기 속도는 음수가 될 수 없고, 달리기 속도는 광속을 초과할 수 없다는 물리 법칙 정도만 명시해놓을 수 있을 뿐이죠.
이제 각각 인터페이스, 추상클래스, 일반 클래스로 인스턴스의 타입을 담아보겠습니다.
이렇게되면 타입의 의도를 더욱 분명히 알수 있습니다.
모두 인스턴스화가 되었음에도 불구하고 각자가 관심이 있는, 책임이 있는 것만 보겠다(SRP원칙)라는 것을 명시하는 효과가 있는 것이죠.
public void example(){
Eating actOfEatingByDog = new Dog("black"); //먹는 행위 중 개의 행위에 주목
actOfEatingByDog.bite();
actOfEatingByDog.digest();
// actOfEatingByDog.bark(); 호출 불가능 -> 먹는 행위의 관점에서 짖는 것은 모른다..
// actOfEatingByDog.color; 접근 불가능 -> 먹는 행위 관점에서는 색깔이라는 상태를 모름..
Mammal amongMamalsDog = new Dog("yellow"); //포유류 중 개를 주목
actOfEatingByDog.bite(); // 먹고
amongMamalsDog.respire(); // 숨쉬고
amongMamalsDog.bark(); //짖고
// amongMamalsDog.color; // 그러나 무슨 색인지는 몰라.. -> 상태를 갖을 수 없음
Dog badook = new Dog("blue"); //우리 바둑이..
badook.bite(); //먹고
badook.respire(); //숨쉬고
badook.bark(); //짖고
String colorOfBabook = badook.color; //우리 바둑이는 색깔도 뭔지 알아..
}
이것이 바로 객체지향이 가진 상속, 다형성의 엄청난 이점입니다. 이것은 스프링의 의존성 주입과 더해져서 매우 강력한 기능을 하게 되죠.(이렇게 아름다운 존재인 .. 객체지향... 그리고 그 뼈대인 인터페이스를 천사..? 라고 부를 수 있지 않을까여..?)
위의 예시코드에서 볼 수 있듯이, 우리가 이전에도 많이 들어보았던
“인터페이스와 추상클래스 그 자체는 인스턴스가 될 수 없고 상태를 갖을 수 없다”
라는 문구는 너무나 당연한 말이었던 것입니다.
단순히 우리가 살고 있는 세계가 그렇게 구성되어 있기 때문이죠.
“달리기“ 라는 존재가 우리 앞에서 무릎을 굽힐 수 있을까요?
당장 친구한테
- “포유류라는 존재가 내 앞에서 젖을 먹이고 짖고 있어...” 라고 말해볼까요?
- 친구가 최소 인간이라면 “미친거 아님? 무슨 포유류?” 라는 반응이 돌아올 게 뻔해 보입니다…
- 왜냐면 위의 문구는 추상적인 존재가 구체적인 행동을 하고 있다고 말한 것이기 때문이죠.
그렇기! 때문에! 우리는 프로그램 세계에서도 객체지향원칙을 준수해야 합니다.
그렇지 않으면 인간이 이해할 수 있는 범위를 벗어나 버리기 때문이죠...!
자, 이제 맨 처음 언급했던 정적 유틸리티 클래스가 악마인가? 에 대한 의문감을 이해할 수 있을까요?
좀 극단적인 표현이라고 생각이 들기도 하지만
객체지향의 세상에서 다른 속성을 가진 존재는 정말로 악마로 느낄 수도 있지 않을까요?
(“포유류라는 존재가 내 앞에서 젖을 먹이고 짖고 있어...”)
그런데 이런,, 서론이 너무나도 길었습니다..
이번 게시물도 분량 조절에 실패한 것 같습니다…
정적 클래스가 정말로 악마인지 !
추상클래스는 왜 유틸리티 클래스로 사용되고 있는지 !
다음 게시물에서 이어서 알아보도록 하겠습니다…!
'백엔드 > Java' 카테고리의 다른 글
동시성 이슈 처리 및 테스트 방안 - 1 (6) | 2024.02.26 |
---|---|
천사와 악마 사이 추상클래스... - 인터페이스 및 정적 유틸리티 클래스 비교 - 2 (5) | 2024.02.13 |
[이넘아 놀자] 4. @Converter 사용하기 (0) | 2023.09.04 |
[이넘아 놀자] 3. 코드값으로 되어있는 Enum의 경우 (0) | 2023.08.28 |
[이넘아 놀자] 2. 일단 만들까 이넘 (0) | 2023.08.28 |
안녕하세요. 린내입니다~!
오늘은 추상클래스에 대해 알아보려고 합니다.
추상클래스는 그 명칭에서 느껴지듯이.. 실무에서도 상당히 추상적으로 사용되는 느낌이 강한듯한데요.
알듯 말듯한 추상클래스에 대해서 이번 기회에 제대로 정리해보겠습니다.
하지만 사실, 오늘의 주인공은 추상클래스가 아닐수도 있습니다.
이 글의 발단은 아래의 게시글에서 시작되었는데요.
https://stackoverflow.com/questions/3340032/are-utility-classes-evil
Are utility classes evil?
I saw this question: If a "Utilities" class is evil, where do I put my generic code? And I thought, why are utility classes evil? Let’s say I have a domain model that is dozens of classes...
stackoverflow.com
유틸리티 클래스로 사용되는 정적 클래스가 악마..? 라는 무시무시한 속설에 의해 시작되었습니다.
간단하게 말해서, 정적 클래스는 전혀 객체지향적이지 않고 절차지향적이다. 그래서 단점이 매우 많다. 그래서 악마다.. 라는 주장이 개발자들 사이에서 계속 지속되고 있는 것 같아 보였습니다.
추상클래스에 대해 알아보자면서 왜 갑자기 정적 유틸리티 클래스에 대해 말하는지 궁금하시는 분들이 계실거예요.
요근래 스프링에 추가된 유틸리티 클래스는 단순한 정적 클래스로 선언되는 것보다 “추상클래스“로 선언되는 경우가 많고, 기존의 정적 클래스로 추상클래스로 리팩토링하는 경우도 종종 있기 때문입니다.

위의 클래스는 final로 선언된 정적 유틸클래스를 추상 클래스로 리팩토링 한 경우입니다.
이 외에도 PatternMatchUtils, ReflectionUtils, FileCopyUtils, SystemPropertyUtils, StreamUtils 등등의 많은 Utils 클래스가 추상클래스로 선언되어 있음을 알 수가 있습니다.
“추상 클래스는 유틸리티 클래스의 타입으로 주로 쓰인다.”
라는 문구는 공부나 실무를 해오면서 한 번도 들어본 적이 없는 말인데, 실제로는 이런식으로 작성이 왕왕되고 있는 것 같았습니다.
그러면 이제부터, 추상클래스가 정확히 무엇이고 왜 악마라고 까지 불리는 정적 클래스를 대체해 유틸리티성 클래스를 추상클래스로 선언하고 있는지까지 한 번 같이 알아보시죠!
추상클래스의 존재는 상속을 목적으로 한다
여러분은 “추상클래스“하면 어떤 단어가 가장 먼저 떠오르시나요?
저의 경우엔 역시 “상속“이란 단어가 먼저 떠오릅니다.
그도 그럴 것이, 저희가 자바를 처음 배울 때 추상클래스는 항상 상속 및 다형성 관련 챕터에서 볼 수 있었기 때문인데요.
언제나 “인터페이스“에 대한 많은 설명 다음에 추상클래스는 비교적 간략하게 설명되어 있었던 것 같습니다.
그도 그럴것이 인터페이스는 자바, 특히 스프링의 의존성 주입과 더해지면 매우 강력한 기능이 되죠.
(추상클래스도 DI는 가능합니다)
하지만 둘 사이에 비슷한 점이 많아서 항상 헷갈리곤 하는데요..
인터페이스와 추상클래스에서 대해 아주 간략하게 설명을 드릴게요 (이때까짐나 해도 간략하려고 했는데....)
추상화의 정도 - 인터페이스 VS 추상클래스
- 인터페이스
- 다중 상속 가능
- 기능(행위)에 대한 정의 → 기능(행위)에 대해 관심
- 추상클래스
- 다중 상속 불가
- 객체(존재)에 대한 정의 → 객체(존재)에 대해 관심
인터페이스와 추상클래스에는 여러 차이점들이 있지만 무엇보다 가장 큰 기능적인 차이점은 “다중 상속“이 가능하냐, 불가능하냐일 것입니다.
이 차이점은 어디서 오는 것일까요?
다중 상속의 가능 유무는 바로 인터페이스와 추상클래스의 관심이 다르다는 것에 있습니다.
인터페이스는 기능(행위)에 관심이 있고, 추상클래스는 객체(존재)에 관심이 있습니다.
그리고 “기능”에 대한 관점에서 “다중상속”은 객체지향원칙 중 하나인 “단일책임원칙”을 해치지 않는 반면,
객체(존재)에 대한 관점에서 “다중상속“은 “단일책임원칙”을 해치게됩니다.
- 인터페이스 - 행위에 관심 - 다중상속 → 단일책임원칙을 벗어나지 않는다!
- 추상클래스 - 존재에 관심 - 다중상속 → 단일책임원칙을 벗어난다!
이렇게 정리해볼 수 있겠습니다.. 쉽게 와닿지 않으신가요?
그럼 좀 더 이해가 쉽게 예시 코드를 보실까요?

패키지는 이렇게 구성해보았습니다.
각 행위들을 정의한 interface 패키지
각 존재의 상위 분류를 정의한 abtracts 패키지
각 단일 존재를 정의한 clazz 패키지가 있습니다.
- 인터페이스 >>> 추상 클래스 > 클래스
위의 순서대로 추상화 정도를 표현해 볼 수 있습니다.
추상 클래스의 명칭에 “추상”이라는 단어가 들어가 있어서 언뜻 추상의 정도가 클 것이라고 오해할 수도 있지만
인터페이스가 추상클래스보다 추상화 정도가 훨씬 큽니다.
반면, 추상클래스는 일반 클래스보다 약간의 추상화가 되있다고 볼 수 있습니다.
각각의 소스코드를 보실까요?
public interface Breathing { //숨쉬기
// 생략되어 있을 뿐 디폴트 메소드를 제외한 인터페이스의 메소드는 추상 메소드입니다.
abstract void respire(); //호흡하다
}
public interface Diving { //잠수하기
void dive(); // 잠수하다
}
public interface BreathingUnderwater extends Breathing, Diving { //수중호흡하기
void openGills(); // 아가미를 벌린다..
}
public interface Eating { //먹기
void bite(); //씹다
void digest(); //소화하다
}
public interface Running { //달리기
//모든 인스턴스의 멤버는 상수입니다. (static final) 생략가능
//달리는 속도는 음수가 될 수 없다.
static final boolean IS_SPEED_MINUS = false;
//광속을 초과할 순 없다
int MAX_SPEED = 299792458;
void bendKnees(); //무릎을 굽히다.
void jump(); //점프하다.
}
- 위처럼 인터페이스는 각각의 기능 정의서라고 볼 수 있습니다.
- 추상화가 목적이기에 기능 정의 의외의 기능 상세(구현 메소드)나 대상의 상태(인스턴스 변수)는 원칙적으로는 갖고 있을 순 없습니다.
- 자바8 이상 디폴트 메소드, 정적메소드 선언이 가능해졌지만 호환성을 위한 목적으로 인터페이스의 주요 기능이 될 순 없습니다.
- https://stackoverflow.com/questions/25784417/why-we-need-default-methods-in-java
Why we need default methods in Java?
I'm taking a look to Java 8 news compared to 7 and in addition to very interesting things like lambdas or the new time framework, i found that a new feature(?) was introduced: default methods. I f...
stackoverflow.com
그럼 이를 사용하는 추상 클래스들을 보겠습니다.
// 어류
abstract public class Fish implements BreathingUnderwater, Eating { //다중상속
boolean isSeeFish;
// 멤버 변수를 갖을 수 있으나 생성자를 직접사용하지 못하고 상속을 통한 일반 클래스에서 초기화 가능
// -> 상속을 통하여 상태를 갖을 수 있다.
public Fish(boolean isSeeFish) {
this.isSeeFish = isSeeFish;
}
// 어류는 추상클래스에서 구현메소드 작성해보지 않겠습니다..
}
// 포유류
abstract public class Mammal implements Breathing, Eating, Running { //다중상속
void breastfeed(){
System.out.println("쪽쪽");
// 젖을 먹이다.. (공통)
}
// 인터페이스 보다는 추상화의 정도가 작지요..?
public abstract void bark(); // 짖는 방법은 다 다릅니다..
// 포유류는 인터페이스의 추상 메소드를 추상클래스 다 구현한다고 해볼까요?
@Override
public void respire() {}
@Override
public void bite() {}
@Override
public void digest() {}
@Override
public void bendKnees() {}
@Override
public void jump() {}
}
위에서 볼 수 있듯 추상클래스를 통해 인터페이스에서 정의 했던 기능들의 사용처를 묶어주고 추상화 정도를 낮춰주었습니다.
두 추상 클래스는 어류와 포유류라는 추상화된 존재를 정의하고 있는데, 어떻게 보이시나요?
각각 인터페이스를 다중 상속을 하고 있음에도 불구하고 단일책임원칙을 헤치지 않는 것을 느낄 수 있으신가요?
인터페이스는 존재가 아닌, 존재에 대한 행위를 정의한 것이기 때문입니다.
그럼 이제 일반 클래스 레벨로 추상화 레벨을 낮춰보겠습니다.
public class GoldenFish extends Fish {
//추상클래스가 상태를 갖으려면 상속을 통해 인스턴스가 되어야함!
public GoldenFish(boolean isSeeFish) {
super(isSeeFish);
}
// 추상 클래스에서 추상메소드 구현을 하지 않았기 때문에 일반 클래스에서 구현 필요
@Override
public void respire() {}
@Override
public void openGills() {}
@Override
public void dive() {}
@Override
public void bite() {}
@Override
public void digest() {}
}
public class Dog extends Mammal {
public public String color;
public Dog(String color) {
this.color = color;
}
// 나머지 추상 메소드들은 추상메소드에서 구현했기 때문에 그냥 상속을 받아 사용 가능
@Override
public void bark() {
System.out.println("멍멍");
}
}
클래스에서는 이제 추상화 레벨이 많이 낮아 졌습니다.
추상클래스에서도 구현을 해놓지 않은 추상 메소드도 클래스 레벨에서는 다 구현을 해주어야합니다.
이제 충분히 추상적이지 않고 구체적 대상으로 존재 가능하니, 구체화를 해준다고 생각하면 될 것 같습니다..
그 다음으로, 추상화 레벨의 제일 아래, 제일 구체적인 대상에 대한 표현을 해보자면 어떻게 할 수 있을까요?
public void example(){
// 추상클래스의 멤버변수는 상속을 통해 인스턴스화 되어야 초기화 가능함 -> 추상클래스 자체로 상태를 갖을 수 없다!
GoldenFish myGoldenFish = new GoldenFish(false);
// Dog는 객체, myDog은 인스턴스
Dog myDog = new Dog("brown");
Dog yourDog = new Dog("white");
}
자바로 표현할 수 있는 가장 구체적인 레벨은 바로 “인스턴스”입니다.
“우리집 강아지는 갈색 털을 갖고 있다.”
이제 정말로 단일한, 구체적인 존재에 대한 표현까지 해볼 수 있게 되었습니다.
단일한 존재인 인스턴스가 되면 위에서 보시듯 당연히 컬러라는 상태를 갖을 수 있게 됩니다.
추상클래스는 그 자체로 상태를 갖을 순 없지만,
일반클래스로 상속을 통하여 인스턴스가 될 수 있고 상태를 갖게될 수 있습니다.
“내 금붕어는 바다고기가 아니다“
추상클래스에서 정의했던 상태를 갖을 수 있게 되었지만
위에서 표현했던 클래스의 상태보다는 조금 더 넓은 범위의 추상화라는 것을 알 수 있죠?
그렇다면 위 인터페이스 예시 중 이건 어떻게 이해해볼 수 있을까요?
public interface Running { //달리기
//모든 인스턴스의 멤버는 상수입니다. (static final) 생략가능
//달리는 속도는 음수가 될 수 없다.
static final boolean IS_SPEED_MINUS = false;
//광속을 초과할 순 없다
int MAX_SPEED = 299792458;
void bendKnees(); //무릎을 굽히다.
void jump(); //점프하다.
}
인스턴스는 일반 멤버 변수를 갖을 수 없고, 불변 상수만 갖을 수 있습니다.
그런데 이는 당연한 것입니다.
뛰기라는 기능 정의가 순간적인 존재의 상태를 갖고 있을 수 없습니다.
다만 정적인 정의 혹은 정보만 갖고 있을 수 있을 뿐입니다.
달리기 속도는 음수가 될 수 없고, 달리기 속도는 광속을 초과할 수 없다는 물리 법칙 정도만 명시해놓을 수 있을 뿐이죠.
이제 각각 인터페이스, 추상클래스, 일반 클래스로 인스턴스의 타입을 담아보겠습니다.
이렇게되면 타입의 의도를 더욱 분명히 알수 있습니다.
모두 인스턴스화가 되었음에도 불구하고 각자가 관심이 있는, 책임이 있는 것만 보겠다(SRP원칙)라는 것을 명시하는 효과가 있는 것이죠.
public void example(){
Eating actOfEatingByDog = new Dog("black"); //먹는 행위 중 개의 행위에 주목
actOfEatingByDog.bite();
actOfEatingByDog.digest();
// actOfEatingByDog.bark(); 호출 불가능 -> 먹는 행위의 관점에서 짖는 것은 모른다..
// actOfEatingByDog.color; 접근 불가능 -> 먹는 행위 관점에서는 색깔이라는 상태를 모름..
Mammal amongMamalsDog = new Dog("yellow"); //포유류 중 개를 주목
actOfEatingByDog.bite(); // 먹고
amongMamalsDog.respire(); // 숨쉬고
amongMamalsDog.bark(); //짖고
// amongMamalsDog.color; // 그러나 무슨 색인지는 몰라.. -> 상태를 갖을 수 없음
Dog badook = new Dog("blue"); //우리 바둑이..
badook.bite(); //먹고
badook.respire(); //숨쉬고
badook.bark(); //짖고
String colorOfBabook = badook.color; //우리 바둑이는 색깔도 뭔지 알아..
}
이것이 바로 객체지향이 가진 상속, 다형성의 엄청난 이점입니다. 이것은 스프링의 의존성 주입과 더해져서 매우 강력한 기능을 하게 되죠.(이렇게 아름다운 존재인 .. 객체지향... 그리고 그 뼈대인 인터페이스를 천사..? 라고 부를 수 있지 않을까여..?)
위의 예시코드에서 볼 수 있듯이, 우리가 이전에도 많이 들어보았던
“인터페이스와 추상클래스 그 자체는 인스턴스가 될 수 없고 상태를 갖을 수 없다”
라는 문구는 너무나 당연한 말이었던 것입니다.
단순히 우리가 살고 있는 세계가 그렇게 구성되어 있기 때문이죠.
“달리기“ 라는 존재가 우리 앞에서 무릎을 굽힐 수 있을까요?
당장 친구한테
- “포유류라는 존재가 내 앞에서 젖을 먹이고 짖고 있어...” 라고 말해볼까요?
- 친구가 최소 인간이라면 “미친거 아님? 무슨 포유류?” 라는 반응이 돌아올 게 뻔해 보입니다…
- 왜냐면 위의 문구는 추상적인 존재가 구체적인 행동을 하고 있다고 말한 것이기 때문이죠.
그렇기! 때문에! 우리는 프로그램 세계에서도 객체지향원칙을 준수해야 합니다.
그렇지 않으면 인간이 이해할 수 있는 범위를 벗어나 버리기 때문이죠...!
자, 이제 맨 처음 언급했던 정적 유틸리티 클래스가 악마인가? 에 대한 의문감을 이해할 수 있을까요?
좀 극단적인 표현이라고 생각이 들기도 하지만
객체지향의 세상에서 다른 속성을 가진 존재는 정말로 악마로 느낄 수도 있지 않을까요?
(“포유류라는 존재가 내 앞에서 젖을 먹이고 짖고 있어...”)
그런데 이런,, 서론이 너무나도 길었습니다..
이번 게시물도 분량 조절에 실패한 것 같습니다…
정적 클래스가 정말로 악마인지 !
추상클래스는 왜 유틸리티 클래스로 사용되고 있는지 !
다음 게시물에서 이어서 알아보도록 하겠습니다…!
'백엔드 > Java' 카테고리의 다른 글
동시성 이슈 처리 및 테스트 방안 - 1 (6) | 2024.02.26 |
---|---|
천사와 악마 사이 추상클래스... - 인터페이스 및 정적 유틸리티 클래스 비교 - 2 (5) | 2024.02.13 |
[이넘아 놀자] 4. @Converter 사용하기 (0) | 2023.09.04 |
[이넘아 놀자] 3. 코드값으로 되어있는 Enum의 경우 (0) | 2023.08.28 |
[이넘아 놀자] 2. 일단 만들까 이넘 (0) | 2023.08.28 |