JAVA

람다 feat. 인터페이스와 추상클래스

6uiw 2025. 5. 26. 20:05

다음 코드를 리팩토링 해보자. 

아래의 코드는 어떠한 작업에 대해 시작 시간과 종료 시간을 측정한 뒤 총 실행 시간을 출력하는 코드이다. 

하지만 시간을 측정하고 출력하는 데에 중복 코드가 발생한다. 이를 효율적으로 리팩토링하려면 어떻게 해야 할까? 

package lambda.start;
import java.util.Random;

public class Ex1Main {

public static void helloDice() {
    long startNs = System.nanoTime();
    //코드 조각 시작
    int randomValue = new Random().nextInt(6) + 1;
    System.out.println("주사위 = " + randomValue);
    //코드 조각 종료
    long endNs = System.nanoTime();
    System.out.println("실행 시간: " + (endNs - startNs) + "ns");
    }
    
    
    public static void helloSum() {
        long startNs = System.nanoTime();
        //코드 조각 시작
        for (int i = 1; i <= 3; i++) {
        System.out.println("i = " + i);
        }
        //코드 조각 종료
        long endNs = System.nanoTime();
        System.out.println("실행 시간: " + (endNs - startNs) + "ns");
    }
    
    
    public static void main(String[] args) {
        helloDice();
        helloSum();
    }
}

 

 

반복 되는 코드는 아래와 같다.

public static void ??? {
    long startNs = System.nanoTime();

     //작업 내용

    long endNs = System.nanoTime();
    System.out.println("실행 시간: " + (endNs - startNs) + "ns");
}

 

작업 내용이 단순 원시 타입인 경우 변수로 빼서 출력해주면 되지만 여기선 작업 내용이 함수이기 때문에 그렇게 쉽게 분리될 것 같지 않다.

 

추상 클래스를 이용해보기로 했다.

추상 클래스에 시간을 측정하는 코드들을 넣고, 중간에 task()로 실행할 작업을 추상메서드로 정의한 후

이를 상속받아 task()에 원하는 작업을 오버라이딩 해서 작업을 정의하고자 했다. 

package lambda.start;

public abstract class checkTime {
    abstract void task();
    
    public final void run() {
        long startNs = System.nanoTime();
        task();
        long endNs = System.nanoTime();
        System.out.println("실행 시간: " + (endNs - startNs) + "ns");

    }

}

 

package lambda.start;

import java.util.Random;

public class Ex1RefMain {

    public static class HelloDice extends checkTime {
        @Override //task 재정의
        public void task() {
            int randomValue = new Random().nextInt(6) + 1;
            System.out.println("주사위 = " + randomValue);
        }

    }

    public static class HelloSum extends checkTime {
        @Override //task 재정의 
        void task() {
            for (int i = 1; i <= 3; i++) {
                System.out.println("i = " + i);
            }
        }
    }

    public static void main(String[] args) {
        checkTime helloDice = new HelloDice();
        helloDice.run();

        HelloSum helloSum = new HelloSum();
        helloSum.run();



    }

}

기능이 잘 작동한다.

하지만 이 방법에서 checkTime은 '클래스'이므로 단일 상속만 가능하다. 이미 다른 클래스를 상속 중이라면 사용할 수 없어 유연성이 떨어진다. 

 

또한 checkTime의 run() 안에 모든 코드의 실행 흐름이 고정되어 있어 외부에서 조립하거나 제어할 수가 없다. (새로운 작업을 추가하기 번거로움)  

 

그리고 task() 메서드가 클래스 안에 묶여 있어 클래스 객체를 통해서만 접근이 가능하여 함수형 프로그래밍이 어렵다..(함수 자체를 다른 함수에 넘길 수 없음)


 

그렇다면, 한번 인터페이스 기반으로 설계를 변경해보자.

인터페이스에는 작업 메서드만 정의해준다.  

package lambda;

public interface Procedure {
    void run();
}

 

 

1. 시간 측정 로직은 외부 함수로 뺀다. 

2. 실행할 작업은 사용자 정의 코드로 남겨둔다. 

3. Procedure 인터페이스를 상속받아 run에 실행할 작업을 재정의한다. 

 

package lambda.start;

import lambda.Procedure;

import java.util.Random;

public class Ex1Ref2Main {

    public static void hello(Procedure procedure) {
        long startNs = System.nanoTime();
        
        //작업
        procedure.run();
        
        long endNs = System.nanoTime();
        System.out.println("실행 시간: " + (endNs - startNs) + "ns");
    }
    
    static class Dice implements Procedure {
        @Override
        public void run() {
            int randomValue = new Random().nextInt(6) + 1;
            System.out.println("주사위 = " + randomValue);
        }
    }
    
    static class Sum implements Procedure {
        @Override
        public void run() {
            for (int i = 1; i <= 3; i++) {
                System.out.println("i = " + i);
            }
        }
    }

    public static void main(String[] args) {
        Procedure dice = new Dice();
        Procedure sum = new Sum();

        hello(dice);
        hello(sum);
        }
}

 

이렇게 짜면 고차 함수 스타일로 구현이 가능하여 람다나 익명 클래스로 작성이 가능하고 , 인터페이스로 작성했기 때문에 다중 구현이 가능해 유연성이 높아진다.

또한 작업 시간을 측정하는 hello()함수가 외부에서 전달받은 작업을 실행하므로 IOC 형태로 띨 수 있다. 

 

첫번째 코드와 비교하여 유연성도 높고, 다중 구현이 가능해졌다. 

 

어떤 기능을 설계할 때 좀 더 유연하게 조립가능하도록 부품화하는 능력이 아직 부족한 것 같다. 객체지향적으로 설계하는 게 참 어려운 것 같다....  

 

 

 

 

 

 

 

 

 

- 인프런 김영한의 실전 자바 고급 3편 참고

'JAVA' 카테고리의 다른 글

JAVA Calendar, LocalDate 알아보기  (0) 2024.11.01
JVM(자바 가상 머신)이란?  (0) 2024.11.01