[Java] Class - Abstract Class를 사용하는 이유
Java를 한다고 하면 OOP(Object-Oriented Programming)을 바로 떠올릴 것이다.
OOP는 객체지향 프로그래밍으로 Object, Abstraction, Class, Encapsulation, Polymorphism, Inheritance 6개의 개념을 포괄한다. Java에서 OOP를 빼면 시체라고 할 정도로 Java에서는 핵심이다.
그중 abstract class(추상 클래스)의 용도에 대해 생각해본 것들을 적어둘 생각이다.
완전 밑바닥부터 추상 클래스를 사용하기까지 단계별로 보여주는 것이 확 와 닿을 것 같아서 여러 step을 통해서 보여주고자 한다.
Step 1.
1) FootStopWatch.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package java_20191126.step1;
// 1. 요구사항 : 밀리 세컨드로 경과 시간을 만들어 주세요.
public class FootStopWatch {
long startTime;
long endTime;
public double getElapsedMilliTime() {
return (double) (endTime - startTime) / (double) 1000;
}
public static void main(String[] args) {
FootStopWatch f = new FootStopWatch();
// 1970년 1월 1일부터 지금까지 시간을 밀리세컨드로 반환
f.startTime = System.currentTimeMillis();
for (long i = 0; i < 10_000_000_000l; i++) {
}
f.endTime = System.currentTimeMillis();
double elapsedTime = f.getElapsedMilliTime();
System.out.printf("경과시간 : %.3f", elapsedTime);
System.exit(0);
}
}
|
2) FootNanoStopWatch.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package java_20191126.step1;
// 2. 요구사항 : 나노 세컨드로 경과 시간을 만들어 주세요.
public class FootNanoStopWatch {
long startTime;
long endTime;
long startNanoTime;
long endNanoTime;
public double getElapsedNanoTime() {
return (double) (endNanoTime - startNanoTime) / (double) 1000000000;
}
public static void main(String[] args) {
FootNanoStopWatch f = new FootNanoStopWatch();
// 1970년 1월 1일부터 지금까지 시간을 밀리세컨드로 반환
// f.startTime = System.currentTimeMillis();
f.startNanoTime = System.nanoTime();
for (long i = 0; i < 10_000_000_000l; i++) {
}
// f.endTime = System.currentTimeMillis();
f.endNanoTime = System.nanoTime();
double elapsedTime = f.getElapsedNanoTime();
System.out.printf("경과시간 : %.9f", elapsedTime);
System.exit(0);
}
}
|
정말 허접한 예시를 들어서 살펴보자. 고객이 Stopwatch의 기능을 하는 프로그램을 만들어 달라고 요청을 해왔다고 해보자.
≫고객의 첫 번째 요구사항: "밀리" 세컨드 단위로 경과시간을 측정하는 Stopwatch 프로그램을 만들어주세요.
구현은 어렵지 않다. 1) FootStopWatch.java에 나온 내용대로 구현을 했다. 원하는 결과도 출력해줬다. 그런데 실컷 구현을 했더니 고객이 두 번째 요구사항을 던져준다.
≫고객의 두 번째 요구사항: "나노" 세컨드 단위로 경과시간을 측정하는 Stopwatch 프로그램을 만들어주세요.
이것 또한 구현은 어렵지 않다. 2) FootNanoStopWatch처럼 기존의 1) 번 코드를 가지고 응용만 하면 된다.
그런데, 무언가 엄청 조잡한 느낌이 드는 것은 사실이다. "밀리" 단위에서 "나노" 단위로 바뀌었을 뿐인데 바꿔야 할 코드가 많기 때문이다. 필자는 main() method에서는 최종적으로 구현한 프로그램을 실행하는 곳이라 생각하기에 main() 내에서는 실행하는 지시문을 제외하고는 최대한 내용을 간소화하는 것이 좋다고 생각한다.
위의 코드는 단위 하나를 바꾸기 위해 FootStopWatch → FootNanoWatch로 클래스 객체 생성을 다시 해야 하고 시간을 기록하기 위해 currentTimeMillis() → nanoTime()으로 method를 변경해야 하며, 경과시간을 계산하기 위한 method를 다시 지정해야 하고 print 형식도 나노 단위에 맞게 맞춰주어야 한다. 요구사항 중 딱 한 부분이 바뀌었을 뿐인데 얼마나 번거롭고 난잡한 작업인가. 그래서 상속(Inheritance)과 추상 클래스(Abstraction)를 통해서 이 작업을 보기 좋게 바꿔보자.
Step 2.
1) StopWatch.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package java_20191126.step2;
public class StopWatch {
long startTime;
long endTime;
public void start() {
startTime = System.currentTimeMillis();
}
public void stop() {
endTime = System.currentTimeMillis();
}
public double getElapsedMilliTime() {
return (double) (endTime - startTime) / (double) 1000;
}
long startNanoTime;
long endNanoTime;
public void startNano() {
startNanoTime = System.nanoTime();
}
public void stopNano() {
endNanoTime = System.nanoTime();
}
public double getElapsedNanoTime() {
return (double) (endNanoTime - startNanoTime) / (double) 1000000000;
}
}
|
2) StopWatchDemo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package java_20191126.step2;
public class StopWatchDemo {
// 좋은 코드는 main 함수 내의 코드가 최대한 안바뀌는 것
// 고객의 요구에도 main은 안바뀌는 코드가 좋은 코드
public static void main(String[] args) {
StopWatch s = new StopWatch();
s.start();
for (long i = 0; i < 10_000_000_000l; i++) {
}
s.stop();
double elapsedTime = s.getElapsedMilliTime();
System.out.printf("경과시간 : %.3f", elapsedTime);
}
}
|
우선 Step 1에서 FootStopWatch와 FootNanoStopWatch의 공통적인 부분이 어디인지를 살펴보자.
전체적인 과정을 보면
1. FootStopWatch f = new FootStopWatch(); // 처음 객체를 생성하고
2. f.startTime = System.currentTimeMillis(); // 기록을 시작하고(startTime)
3. for (long i = 0; i < 10_000_000_000l; i++) { } // for문을 돌려 시간 경과를 표현하고
4. f.endTime = System.currentTimeMillis(); // 기록을 끝내고(endTime)
5. double elapsedTime = f.getElapsedMilliTime(); // 시간 차이를 계산해서 변수에 할당
6. System.out.printf("경과시간 : %.3f", elapsedTime); // 콘솔에 알맞은 형식으로 print 한다.
위와 같이 총 6개의 단계로 나뉜다. 만약 Step 1처럼 프로그램을 구성했다면 "나노" 단위로 맞춰달라는 고객의 요구에 모든 걸 바꿔야 하는 사태가 발생할 수도 있어서 상당히 비효율적이다.
클래스와 객체를 통일시키기 위해 StopWatch Class를 만들어 두 개의 경우(Milli, Nano)를 통합시켰다. 거기에 기록을 시작하고 끝마치는 두 개의 메서드 "start(), stop()"을 만들고 시간 차이를 계산하는 메서드도 하나의 클래스에 다 집어넣었다. 그렇게 함으로써 main 메소드 내에 내용이 전 단계보다는 조금 더 간결해진 것을 볼 수 있다.
하지만 이런 작업은 Class 관리 측면에서 상당히 비효율적이다. StopWatch Class안에 Milli와 Nano의 경우를 나눠서 무작정 method로 집어넣으면 또 새로운 작업을 의뢰했을 때 Class안에 중복으로 계속 추가를 해야 한다. Class 내 중복은 메모리 관리 측면에서 상당히 치명적이다. 지금 허접한 예시는 2개의 작업만을 표현했지만 실제 기업의 요구사항을 처리하려고 한다면 수많은 기능들이 추가될 것이다. 그것을 무식하게 하나의 Class에 담아서 처리하려고 한다면 다음날 직장에서 본인의 자리가 사라질 것이다.
다음 단계에서 Class를 분리해서 보기 좋게 바꿔보자
Step 3.
1) StopMilliWatch.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package java_20191126.step3;
public class StopMilliWatch {
// StopNanoWatch class의 맴버변수와 겹친다.
// 나중에 새로운 클래스 만들시 또 이 맴버변수를 만들어주어야 한다.
private long startTime;
private long endTime;
// 아래 3개의 method도 StopNanoWatch class와 비슷하다.
public void start() {
startTime = System.currentTimeMillis();
}
public void stop() {
endTime = System.currentTimeMillis();
}
public double getElapsedTime() {
return (double) (endTime - startTime) / (double) 1000;
}
// 해결책
// 추상클래스를 통해서 공통된 것들을 추상화 시키면 된다.
// 나중에 유지보수에서 한 class만 수정하면 나머지는 자동적으로 수정되도록 하는 것이 좋다.
}
|
2) StopNanoWatch.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package java_20191126.step3;
public class StopNanoWatch {
private long startTime;
private long endTime;
public void start() {
startTime = System.nanoTime();
}
public void stop() {
endTime = System.nanoTime();
}
public double getElapsedTime() {
return (double) (endTime - startTime) / (double) 1000000000;
}
}
|
3) StopWatchDemo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package java_20191126.step3;
public class StopWatchDemo {
public static void main(String[] args) {
StopNanoWatch s = new StopNanoWatch();
s.start();
for (long i = 0; i < 10_000_000_000l; i++) {
}
s.stop();
double elapsedTime = s.getElapsedTime();
System.out.printf("경과시간 : %.9f", elapsedTime);
}
}
|
StopMilliWatch와 StopNanoWatch 클래스로 분리했다. 새로운 요구사항이 들어오면 새로운 Class를 만들어서 프로그램을 구성하면 될 것이다. 다음 단계에서는 main 내의 부분을 더 간소화해보자.
사실 다음 Step에서는 본래 말하고 싶었던 추상 클래스를 이용해서 코드를 작성한 것을 보여주고자 한다.
2부에서 계속
'Java' 카테고리의 다른 글
[Java] Thread에 대한 공부 (0) | 2019.12.05 |
---|---|
[Coding] 달력 출력하기 (Java) (0) | 2019.12.02 |
[Java] equals & hashCode - 두 method에 대한 공부 (0) | 2019.12.02 |
[Java] Access Modifier - 접근한정자에 대한 공부 (0) | 2019.12.02 |
[Java] Class - Abstract Class를 사용하는 이유(두 번째) (0) | 2019.11.28 |
댓글
댓글(Github)
다른 글
-
[Coding] 달력 출력하기 (Java)
[Coding] 달력 출력하기 (Java)
2019.12.02 -
[Java] equals & hashCode - 두 method에 대한 공부
[Java] equals & hashCode - 두 method에 대한 공부
2019.12.02 -
[Java] Access Modifier - 접근한정자에 대한 공부
[Java] Access Modifier - 접근한정자에 대한 공부
2019.12.02 -
[Java] Class - Abstract Class를 사용하는 이유(두 번째)
[Java] Class - Abstract Class를 사용하는 이유(두 번째)
2019.11.28