글 작성자: beaniejoy

전 단계에 이어서 보겠다.

 

📌 1. Step 4.

 

🔖 1-1. StopMilliWatch

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;
    }
}

 

🔖 1-2. StopNanoWatch

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;
    }
}

 

🔖 1-3. StopWatchDemo

public class StopWatchDemo {
    // main을 줄인 것은 좋으나
    // 공통된 것이 아직 존재
    // overloading 이용해서
    public static void execute(StopMilliWatch s) {
        s.start();

        for (long i = 0; i < 10_000_000_000l; i++) {

        }
        s.stop();
        double elapsedTime = s.getElapsedTime();
        System.out.printf("경과시간 : %.3f", elapsedTime);
    }

    public static void execute(StopNanoWatch s) {
        s.start();

        for (long i = 0; i < 10_000_000_000l; i++) {

        }
        s.stop();
        double elapsedTime = s.getElapsedTime();
        System.out.printf("경과시간 : %.9f", elapsedTime);
    }

    public static void main(String[] args) {
        // 이렇게 하면 새로운 클래스 객체가 나올 때마다
        // 새로이 선언을 해주어야 한다.
        StopMilliWatch s1 = new StopMilliWatch();
        execute(s1);

        StopNanoWatch s2 = new StopNanoWatch();
        execute(s2);
    }
}

 StopMilliWatch, StopNanoWatch 두 개의 Class는 전 단계하고 차이가 없이 그대로 사용했다. 사실 이번 단계는 굳이 거쳐갈 필요는 없다고 생각하지만 이해를 위해 추가를 했다. 달라지는 부분은 3) StopWatchDemo Class다.

 앞에서 언급했지만 본인이 생각하는 좋은 코딩은 main에서 하나의 실행코드로 전체 프로그램을 실행할 수 있도록 간결하게 짜는 것이다. 전 단계에서 main 내용을 보면 start로 시작해 for문을 돌리고 stop을 해서 결과를 출력한다. 이것마저 main내에서는 번잡해 보이기 때문에 본인은 이 내용도 Class안에 다 집어넣기로 했다. 같은 Class내에서 main의 내용을 위로 빼서 static method로 정의했다. main내의 코드가 확 줄었음을 확인할 수 있다.

 하지만 여기서 문제는 새로운 요구사항을 보낼 때마다 새로운 class이름으로 객체를 선언해야 한다는 문제와 execute() method가 쓸데없이 중복이 된다는 것이다. 마지막 최종 단계에서 추상 클래스를 이용해 이를 해결해보자.

 

📌 2. Step 5.

 

🔖 2-1. StopMilliWatch

public class StopMilliWatch extends StopWatch {

    private void start() {
        setStartTime(System.currentTimeMillis());
    }

    private void stop() {
        setEndTime(System.currentTimeMillis());
    }

    private double getElapsedTime() {
        return (double) (getEndTime() - getStartTime()) / (double) 1000;
    }

    public void run() {
        start();
        for (long i = 0; i < 10_000_000_000l; i++) {

        }
        stop();
        double elapsedTime = getElapsedTime();
        System.out.printf("경과시간 : %.3f", elapsedTime);
    }
}

 

🔖 2-2. StopNanoWatch

public class StopNanoWatch extends StopWatch {

    private void start() {
        setStartTime(System.nanoTime());
    }

    private void stop() {
        setEndTime(System.nanoTime());
    }

    private double getElapsedTime() {
        return (double) (getEndTime() - getStartTime()) / (double) 1000000000;
    }

    public void run() {
        start();

        for (long i = 0; i < 10_000_000_000l; i++) {

        }
        stop();
        double elapsedTime = getElapsedTime();
        System.out.printf("경과시간 : %.9f", elapsedTime);
    }
}

  두 개의 클래스를 비교해보면 다른것은 제쳐두고 없어서는 안될 method인 run() 메서드가 공통으로 존재한다. 이는 상위 객체에서 강제할 수 있게끔 메서드를 설정해 Overriding을 꼭 하게끔 장치를 설정하는 것이 좋다. 여기서 추상 클래스의 강력함이 나온다. 

 

🔖 2-3. StopWatch

// abstract는 일반 부모 class와 달리
// 강제성을 띄기 때문에 공통method를 추상화하는 것이 좋다.
public abstract class StopWatch {
    // 밑에서부터 봤을 때 공통된 것들을 부모로 올린다.
    // Encapsulation
    private long startTime;
    private long endTime;

    public long getStartTime() {
        return startTime;
    }

    public void setStartTime(long startTime) {
        this.startTime = startTime;
    }

    public long getEndTime() {
        return endTime;
    }

    public void setEndTime(long endTime) {
        this.endTime = endTime;
    }

    public abstract void run();
}

 StopWatch라는 추상 클래스를 만들어 abstract void run() 이라는 메서드를 설정했다. 이제는 이 추상 클래스를 상속받는 어떠한 클래스든 run() 메서드는 꼭 Overriding해야 한다.

 

🔖 2-4. StopWatchDemo

public class StopWatchDemo {
    // static main에 사용하는 것이므로 static method화
    public static void execute(StopWatch s) {
        s.run();
    }
    // java reflection을 사용함에 있어서 예외처리 해야함
    public static void main(String[] args) throws Exception {
        String input = args[0];
        input = "java_20191126.step5." + input;

        // 본래 문자열을 가지고 클래스 객체를 선언할 수 없다.
        // newInstance(): 객체화 하는것
        // java reflection
        // 문자열을 통해서 객체를 만들 수 있다.
        // (단 package명도 포함해서 class이름을 넣어야한다.)
        StopWatch s1 = (StopWatch) Class.forName(input).newInstance();
        // s1.run();
        execute(s1);
    }
}

 Demo class에서 main함수를 요구사항에 맞춰 입력받기 편하게 코드를 작성했다. 이제는 입력값으로 해당 요구사항에 맞는 클래스명만 입력하면 알아서 결과를 출력해준다. Step 1과 비교했을 때 상당히 간편해진 것이다. 

 Step 1부터 5까지 살펴보았지만 코드 자체는 엉성하고 잘짜여진 코드는 확실히 아니다. 위의 예시들은 단순히 추상 클래스의 위력과 이를 이용해 얼마나 간편하게 프로그램을 구성할 수 있는지 보여주기 위함이다. 우선 Abstract Class의 장점은 여러가지가 있을 수 있지만 

  1. 개발자입장에서 상속받아 사용할 때 구현이 필수적인 메서드를 강제할 수 있다는 점
    - public abstract void run();
  2. 공통된 method를 한꺼번에 묶어줌으로써 객체를 받을 때 공통된 추상클래스로 받을 수 있다는 점
    - execute(StopWatch s)에서 공통된 parameter로 넘길 수 있어서 하나로 통합 가능
  3. 수천개의 클래스를 관리할 때도 체계적이고 가독성이 높게 개발할 수 있다는 점

이렇게 3가지로 압축할 수 있을 것 같다.