#1 교육정리
1) 추상클래스
#2 추상클래스
자바에서는 하나 이상의 추상 메소드를 포함하는 클래스를 가리켜 추상 클래스(abstract class)라고 합니다.
이러한 추상 클래스는 객체 지향 프로그래밍에서 중요한 특징인 다형성을 가지는 메소드의 집합을 정의할 수 있도록 해줍니다.
추상적이라는 것은 구체적이지 않고 막연한 것을 뜻합니다. 즉, 추상클래스라는 의미는 구체적이지 않은 클래스라는 뜻입니다. 그리고 추상 클래스가 아닌 대부분의 클래스는 concrete class라고 합니다.
추상 클래스는 항상 추상 메서드를 포함합니다. 추상 메서드는 구현 코드가 없습니다. 함수의 구현 코드가 없다는 것은 함수 몸체(body
)가 없다는 뜻입니다.
추상메서드)
추상메서드를 조금 더 자세히 알아보자면 아래와 같습니다.
추상 메소드(abstract method)란 자식 클래스에서 반드시 오버라이딩해야만 사용할 수 있는 메소드를 의미합니다.
자바에서 추상 메소드를 선언하여 사용하는 목적은 추상 메소드가 포함된 클래스를 상속받는 자식 클래스가 반드시 추상 메소드를 구현하도록 하기 위함입니다.
예를 들면 모듈처럼 중복되는 부분이나 공통적인 부분은 미리 다 만들어진 것을 사용하고, 이를 받아 사용하는 쪽에서는 자신에게 필요한 부분만을 재정의하여 사용함으로써 생산성이 향상되고 배포 등이 쉬워지기 때문입니다.
이러한 추상 메소드는 선언부만이 존재하며, 구현부는 작성되어 있지 않습니다.
바로 이 작성되어 있지 않은 구현부를 자식 클래스에서 오버라이딩하여 사용하는 것입니다.
추상메서드 문법 :
abstract 반환타입 메소드이름();
위와 같이 선언부만 있고 구현부가 없다는 의미로 선언부 끝에 바로 세미콜론(;)을 추가합니다.
concrete 문법)
int add(int x, int y) {
return x + y;
}
abstract 문법)
위에 {} 중괄호로 감싼 부분을 함수의 구현부(implementation)라고 하는데, 이 부분이 없는 함수는 추상 함수(abstract function)이고 자바에서는 추상 메서드라고 합니다. 추상 메서드는 다음과 같이 선언만 하며 abstract 예약어를 사용합니다.
그리고 {} 대신 ;를 사용합니다.
abstract int add(int x, int y);
아울러 개발을 하며 변수를 선언하고 제어문을 사용하여 로직을 만들고 기능을 구현하는 것도 중요하지만, 어떻게 구현할지를 결정하는 것 또한 중요합니다. 즉, 메서드의 선언부(declatration)만 봐도 어떤 일을 하는 메서드인지 알 수 있게 결정하는 것이 중요합니다.
자바에서도 우리가 자바에서 사용하는 메서드를 선언한다는 것은 메서드가 해야 할 일을 명시해두는 것입니다.
추상클래스 사용 목적 및 이유)
목적 :
자바에서 추상 메소드를 선언하여 사용하는 목적은 추상 메소드가 포함된 클래스를 상속받는 자식 클래스가 반드시 추상 메소드를 구현하도록 하기 위함입니다.
만약 일반 메소드로 구현한다면 사용자에 따라 해당 메소드를 구현할 수도 있고, 안 할 수도 있습니다.
하지만 추상 메소드가 포함된 추상 클래스를 상속받은 모든 자식 클래스는 추상 메소드를 구현해야만 인스턴스를 생성할 수 있으므로, 반드시 구현하게 됩니다.
이유 :
이유는 크게 3가지로 분류해보겠습니다.
1. 공통된 필드와 메서드를 통일할 목적
10명 개발자에게 자동차를 상속받아 각자만의 실체클래스를 구현하라고 주문해보자.
10명 개발자가 생각한 변수명과 메서드명은 제각기 다른 이름을 가지고 구현될 것이다. 이렇게 구현이 되면 문제가 있다. 만약, 수만줄에 이르는 코드에 A라는 자동차 실체클래스 객체를 선언하고 해당 객체의 필드와 메서드를 떡칠했다고 치자. 헌데, A자동차가 계약만료되고, B자동차를 새로 교체해야한다고 하자
만약 B자동차의 변수와 메서드명이 A자동차와 동일하면 객체 인스턴스만 변경하면 되는데, 필드와 메서드를 전부다 체크해서 변경해줘야한다. 유지보수는 개뿔 이건 아예 새로 개발하는 느낌일 것이다. 따라서 추상클래스를 만든다! 즉, 추상클래스에서 미리 정의한 필드와 메서드가 있다면, 실체클래스는 추상클래스의 필드와 메서드명을 변경할 수 없고 무조건 해당 명명으로 구현해야한다. 따라서, 필드와 메서드 이름을 통일하여 유지보수성을 높이고 통일성을 유지하고 가독성을 높일 수 있습니다..
2. 실체클래스 구현시, 시간절약
추상클래스추상 클래스 또한 객체이기 때문에 클래스를 상속하면서 누릴 수 있는 이점들을 추상 클래스도 그대로 누릴 수 있습니다. 예를 들어 많은 객체들을 생성할 때 모든 객체들에 공통적인 메서드와, 필드들을 추상 클래스로 만들고 이 추상 클래스를 하위 자식들이 상속받아 필요한 필드와 메서드를 그대로 상속하고 추상 클래스는 자식에서 재정의하는 방식을 통해 개발의 효율성을 증대시킬 수 있습니다.
실무적으로 생각해보면, 나는 SI개발자이다. 그런데 개발해야하는데 시간이 너무 없습니다... 그러던 와중에 갑자기 나보고 자동차라는 어마무시한 클래스를 일주일 안에 구현하라고한다. 그럼 설계부터 생각한다. 자동차는 바퀴가 있어야하고, 굴러가야하고 아 백미러도 있어야하고 트렁크도 있어야하고...
여기서 추상클래스는 효과를 발휘한다. 내가 자동차를 구현해야하는데, 자동차 추상클래스를 상속받으면, 자연스럽게 자동차에 공통적으로 들어가야하는 필드와 메서드가 녹여져있는 필드와 메서드가 오버라이딩 된다. 즉, 강제로 주어지는 필드와 메서드를 가지고 나만의 스타일대로 구현만 하면 된다. 그러므로 설계 시간이 절약되고 구현하는데만 집중할 수 있다!
3. 규격에 맞는 실체클래스 구현
시간절약과 비슷할 수도 있는 내용이다. 이미 설명했지만, 아무리 자기 스타일대로 클래스를 구현한다고 해도 그것도 결국엔 규격안에서 구현하는 것을 허락한다는 것이지, 규격도 없이 아무렇게나 구현을 해서는 안된다. 이유는 개발은 혼자서 하는 일이 아니다. 모두가 약속한 필드와 메서드 그리고 설계 규칙에 녹아져있는 규격에 맞는 클래스를 구현해야한다. 그래야 코드 수정시, 영향도를 적게 가져가면서 유지보수성을 높일 수 있다. 따라서 선임 설계개발자가 코드 전반적인 부분을 잡아주고, 해당 규격에 맞게 클래스를 구현하면 된다.
여기서 추상클래스의 강력한 기능이 나온다. 추상클래스를 상속받은 실체클래스들은 반드시 추상메서드를 재정의(오버라이딩)해서 실행 내용을 작성해야 한다. 만약 그렇지 않으면 컴파일 에러를 발생시켜 실행조차 못하게 막는다. 따라서, 코더들은 강제적으로 추상메서드를 구현해야한다. 여기서 추상메서드라는 것이 갑자기 나왔는데, 추상클래스 안에 abstract 키워드를 가지고 있는 메서드는 추상메서드라고 하고, 상속시 반드시 재정의해야하는 메서드라는 뜻이다.
중요한건, 소스 수정시 다른 소스의 영향도를 적게 가져가면서 변화에는 유연하게 만들기 위해 추상클래스를 사용하기도 한다. 규격에 맞게 소스가 구현되어 있기 때문에 해당 규격에 대한 구현부만 수정하면 손 쉽게 수정이 가능하기 때문이다.
예시참조 : https://limkydev.tistory.com/188
다음은 코드와 함께 추상클래스에 대해서 자세하게 알아보겠습니다.
추상 클래스와 인스턴스)
// 추상 클래스와 인스턴스
package com.eomcs.oop.ex07.a;
// 클래스 앞에 abstract를 붙인다.
// 추상 클래스의 목적
// - 추상 메서드가 있든 없든 상관없이 추상 클래스를 만들 수 있다.
// - 서브 클래스에게 공통 필드나 메서드를 상속해주는 것이 목적이다.
// - 직접 사용하지 않는다.
// - 여러 클래스를 같은 타입으로 묶기 위함이다.
// - 상속에서 generalization을 통해 수퍼 클래스를 정의하는 경우에
// 그 수퍼 클래스를 주로 추상 클래스로 만든다.
//
abstract class A {
}
public class Exam01 {
public static void main(String[] args) {
// 추상 클래스는 인스턴스를 생성할 수 없다.
// A obj1 = new A(); // 컴파일 오류!
// 그러나 레퍼런스는 선언할 수 있다.
A obj2 = null;
}
}
추상 클래스와 추상 메서드)
// 추상 클래스와 추상 메서드
package com.eomcs.oop.ex07.a;
// 추상 클래스
abstract class A2 {
// 추상 메서드
// - 메서드 선언부에 abstract를 붙인다.
// - 메서드 몸체(body)가 없다.
// - 추상 클래스나 인터페이스에서만 선언할 수 있다.
//
// 추상 메서드의 용도
// - 서브 클래스 마다 구현이 다를 수 있는 경우에 사용한다.
// - 서브 클래스가 반드시 구현해야 하는 메서드가 있다면 추상 메서드로 선언하라!
// - 추상 메서드를 상속 받은 서브 클래스는 반드시 추상 메서드를 구현해야 한다.
// 만약 구현하지 않으면 서브 클래스도 추상 클래스가 되어야 한다.
// 왜?
// 추상 메서드를 갖는 클래스는 오직 추상 클래스만이 가능하기 때문이다.
//
public abstract void m1();
// 추상 메서드는 구현할 수 없다.
// public abstract void m2() {} // 컴파일 오류!
}
// 일반 클래스(concrete class)
class A2Sub extends A2 {
@Override
public void m1() {
// 서브 클래스에서 추상 메서드를 구현해야 한다.
}
}
public abstract class Exam02 extends A2 {
// 서브 클래스에서 추상 메서드를 구현하지 않는다면,
// 추상 메서드인 채로 남아 있기 때문에
// 추상 클래스가 되어야 한다.
}
추상 클래스 레퍼런스와 메서드 호출)
// 추상 클래스 레퍼런스와 메서드 호출
package com.eomcs.oop.ex07.a;
// 추상 메서드는 구현하지 않은 메서드이기 때문에
// 일반 클래스(=구현 클래스; concrete class)는 추상 메서드를 가질 수 없다.
// 오직 추상 클래스만이 추상 메서드를 가질 수 있다.
// 왜?
// - 일반 클래스는 인스턴스를 생성할 수 있다.
// - 인스턴스로 메서드를 호출하기 때문에
// 일반 클래스에 완전히 정의되지 않은 메서드가 있다면,
// 호출할 때 오류가 발생할 것이다.
// - 이런 문제를 발생시키지 않기 위해
// 일반 클래스는 추상 메서드를 갖지 않게 하였다.
//
abstract class A3 {
public abstract void m1();
}
class A3Sub extends A3 {
@Override // 이 애노테이션은 빼도 된다.
public void m1() {
System.out.println("A3Sub.m1() 호출됨!");
}
public void m2() {
System.out.println("A3Sub.m2() 호출됨!");
}
}
public class Exam03 {
public static void main(String[] args) {
A3 obj;
// 추상 클래스의 인스턴스는 생성 불가!
// obj = new A3(); // Error!
// 추상 메서드를 구현한 서브 클래스 만이 인스턴스 생성 가능!
obj = new A3Sub();
// 오버라이딩 규칙에 따라
// - 레퍼런스가 실제 가리키는 객체의 클래스에서부터 메서드를 찾는다.
obj.m1();
// 참고!
//
// - 레퍼런스가 실제 가리키는 객체가 A3Sub 라 하더라도
// 레퍼런스 타입의 범위를 넘어서 메서드를 호출할 수는 없다.
// obj.m2(); // 컴파일 오류!
// - 물론 실제 인스턴스 타입으로 형벼환 후에는 가능한다.
((A3Sub) obj).m2();
}
static void test(A3 obj) { // A3의 서브클래스의 인스턴스를 받는다는 의미
obj.m1();
// 엥?
// obj는 A3의 레퍼런스이다.
// A3의 m1() 은 추상 메서드이다.
// 그런데 어떻게 m1() 을 호출하는가?
//
// 답변:
// - A3는 추상 클래스이기 때문에 A3의 인스턴스는 생성할 수 없다.
// - 따라서 obj 파라미터에 넘어오는 인스턴스(주소)는
// A3의 인스턴스가 아니라 A3의 자식 클래스의 인스턴스일 것이다.
// - 인스턴스를 만들었다는 의미는 A3의 자식 클래스로서
// A3의 모든 추상 메서드를 구현했다는 의미다.
// - 따라서 obj를 가지고 m1() 를 호출할 때는
// 실제 obj가 가리키는 인스턴스의 클래스에서 m1() 메서드를 찾아 호출한다.
// - 다시 한 번 말하지만 인스턴스를 만들었다는 것은
// 일반 클래스라는 뜻이고,
// 일반 클래스에는 추상 메서드가 없다.
// 그러므로 A3를 상속 받은 일반 클래스는 반드시 m1() 메서드를 구현했다는 의미다.
//
}
}
추상 클래스와 추상 메서드 정리!)
// 추상 클래스와 추상 메서드 정리!
package com.eomcs.oop.ex07.a;
abstract class A4 {
// 추상 클래스의 용도는
// - 서브 클래스들이 가져야할 공통 변수나 메서드를 제공하는 것이다.
// - 그래서 다음과 같이 일반 변수나 메서드를 정의할 수 있다.
public static int value1 = 100;
static void m1() {System.out.println("A4.m1() 호출됨!");}
public String value2 = "Hello!";
void m2() {System.out.println("A4.m2() 호출됨!");}
// 추상 클래스는 추상 메서드도 가질 수 있다.
// 의미?
// - 서브클래스마다 구현이 다를 경우
// 굳이 수퍼 클래스에서 정의할 필요가 없기 때문에
// 완전히 정의하지 않은 추상 메서드로 남겨둔다.
// - 서브클래스에게 메서드 구현을 강제하고 싶을 때도 추상 메서드를 활용한다.
// 추상 메서드를 상속 받은 서브 클래스는 반드시 정의해야 하기 때문이다.
// 서브 클래스가 상속 받은 추상 메서드를 구현하지 않는다면
// 추상 메서드를 그냥 보유하기 때문에 일반 클래스가 될 수 없다.
// 즉 추상 클래스가 되어야 한다.
// - 의미
// "이 메서드는 수퍼 클래스에서 정의해봐야 소용없어.
// 어차피 서브 클래스에서 재정의할 메서드야!"
// "이 메서드는 서브 클래스에서 반드시 정의해야 해.
// 왜냐하면 수퍼 클래스에 정의한 다른 메서드가 이 메서드를 사용하기 때문이야!"
//
public abstract void m3();
}
class A4Sub extends A4 {
@Override // 이 애노테이션은 빼도 된다. 참고, 메서드를 구현하는 것도 오버라이딩으로 본다.
public void m3() {
System.out.println("A4Sub.m3() 호출됨!");
}
}
public class Exam04 {
public static void main(String[] args) {
A4 obj = new A4Sub();
System.out.println(A4.value1);
System.out.println(obj.value2);
A4.m1();
obj.m2();
obj.m3();
}
}
템플릿 메서드)
템플릿(template)란 용어를 사전에 찾아보면, 틀이나 견본을 뜻합니다. 틀이 있는 메서드라는 의미입니다. 이는 Gof의 디자인패턴이며 템플릿 메서드는 추상 클래스를 사용하여 구현할 수 있으며, 다음 코드 예제와 함께 더 알아보겠습니다.
// 추상 클래스와 추상 메서드의 활용 : Templete Method (GoF)의 패턴
package com.eomcs.oop.ex07.a;
abstract class Letter {
// 수퍼 클래스는 서브 클래스에게 구현된 멤버를 상속해준다.
String content;
public void setContent(String content) {
this.content = content;
}
// 수퍼 클래스에서 기능이 어떻게 동작하는지 정의한다.
// => 템플릿의 역할을 하는 메서드를 수퍼 클래스에 둔다.
// => 자세한 구현은 서브 클래스에 맡긴다.
public void print() { // <== 템플릿 메서드 디자인 패턴에서 "템플릿 메서드"에 해당한다.
this.printHeader();
System.out.println(this.content);
System.out.println();
System.out.printf(" From %s!\n", this.getSign());
System.out.println();
this.printFooter();
}
// 세부 사항에 대한 것은
// 서브 클래스에게 구현을 맡긴다.
public abstract void printHeader();
public abstract void printFooter();
public abstract String getSign();
}
// 상세한 기능에 대한 구현은 다음과 같이 서브 클래스에게 맡긴다.
class LoveLetter extends Letter {
@Override
public void printHeader() {
System.out.println("♥♥♥♥♥♥♥♥♥♥♥♥♥♥ [사랑을 그대에게] ♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥");
}
@Override
public String getSign() {
return "당신의 영원한 팔로워, 홍길동";
}
@Override
public void printFooter() {
System.out.println("♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪");
}
public void x() {
}
}
class ReportLetter extends Letter {
@Override
public void printHeader() {
System.out.println("[캠프 강의 보고서]");
}
@Override
public String getSign() {
return "강사 홍길동";
}
@Override
public void printFooter() {
System.out.println("==========================================");
}
}
public class Exam05 {
public static void main(String[] args) {
Letter letter = new LoveLetter();
letter.setContent("눈이 녹으면 무엇이 될까요?\n"
+ "봄이 온다 합니다.\n"
+ "따뜻한 봄이 기다려지네요.");
letter.print();
Letter letter2 = new ReportLetter();
letter2.setContent("강의중!");
letter2.print();
}
}
♥♥♥♥♥♥♥♥♥♥♥♥♥♥ [사랑을 그대에게] ♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥♥
눈이 녹으면 무엇이 될까요?
봄이 온다 합니다.
따뜻한 봄이 기다려지네요.
From 당신의 영원한 팔로워, 홍길동!
♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪
[캠프 강의 보고서]
강의중!
From 강사 홍길동!
==========================================
위와 같이 템플릿 메서드의 역할은 메서드 실행 순서와 시나리오를 정의하는 것입니다. 템플릿 메서드에서 호출하는 메서드가 추상 메서드라면 편지에 따라 구현 내용이 바뀔 수는 있습니다. ReportLetter와 LoveLetter 내용 방식의 일부가 다른 것처럼 말이죠, 하지만, 제목이 있고, 서명이 있고, 맺음이 있다는 편지지의 틀(시나리오)는 변하지 않습니다. 이런 메서드를 템플릿 메서드로 정의하는 것입니다.
템플릿 메서드는 실행 순서, 즉 시나리오를 정의한 메서드이므로 바뀔 수 없습니다. 상위 클래스를 상속받은 하위 클래스에서 템플릿 메서드를 재정의하면 안된다는 것입니다. 그래서 템플릿 메서드는 final 예약어를 사용해 선언합니다. 메서드 앞에 final을 사용하면 상속받은 하위 클래스가 메서드를 재정의할 수 없습니다. 템플릿 메서드는 로직 흐름이 이미 정해져 있는 프레임워크에서 많이 사용하는 기본 구현 방법 입니다.
정리하자면, 추상 클레스는 하위 클래스에서도 사용할 수 있는 코드를 구현합니다. 그런데 일반 메서드는 하위 클래스에서 재정의할 수 있습니다. 템플릿 메서드는 로직 흐름을 정의하는 역할입니다. 이 름은 모든 하위 클래스가 공통으로 사용하고 코드를 변경하면 안되기 때문에 final로 선언하는 것입니다.
(final 예약어는 추후 정리 하도록 하겠습니다....)
'[Naver Cloud Camp 7] 교육 정리' 카테고리의 다른 글
네이버 클라우드 캠프 51일차 230706 (0) | 2023.07.06 |
---|---|
네이버 클라우드 캠프 50일차 230705 (0) | 2023.07.05 |
네이버 클라우드 캠프 48일차 230703 (1) | 2023.07.04 |
네이버 클라우드 캠프 47일차 230630 (0) | 2023.06.30 |
네이버 클라우드 캠프 45일차 230628 (0) | 2023.06.28 |