OCP와 DIP를 지키기 위해
인터페이스와 구현체를 분리하고 다형성을 갖게끔 하는게 좋은 코드란건 알겠고,
결국 인터페이스를 상속받고 필요한 구현체만 구현하면 된다는걸 다들 알겠다고 한다.
 
근데 정작 사용을 어떻게 하는지 대부분 모르는 것 같다.
 
 
일단 개인적으로 그다지 좋지 않은 코드의 예시다.

    public interface PayService {

        void pay();
    }

    private class PayKakaoService implements PayService {

        @Override
        public void pay() {

        }
    }

    private class PaySamsungService implements PayService {

        @Override
        public void pay() {

        }
    }

    private class PayNaverService implements PayService {

        @Override
        public void pay() {

        }
    }

    private PayService payKakaoService;
    private PayService paySamsungService;
    private PayService payNaverService;

    @Getter
    @RequiredArgsConstructor
    public enum PayType {
        Kakao, Samsung, Naver;
    }

    @PostMapping("/payment")
    public ResponseEntity<?> pay(PayType payType) {

        if (payType == PayType.Kakao) {
            payKakaoService.pay();
        }

        if (payType == PayType.Samsung) {
            paySamsungService.pay();
        }

        if (payType == PayType.Naver) {
            payNaverService.pay();
        }

        return ResponseEntity.ok().build();
    }

    // 실행
    void callPayTest() {
        this.pay(PayType.Samsung);
    }

위 코드의 단점이 뭘까? 분명 인터페이스도 썻고, 필요에 따라 구현체도 만들었고,
근데 저기에 앱쁠페이를 추가한다면? 구현체를 만들지만

public ResponseEntity<?> pay(PayType payType)

요기 안에 있는 if문을 추가해야 하지 않겠는가?!
 
이거는 DIP에 위배 되는게 아닌가? 싶기도 하고, 

PayService ps = null;

if (payType == PayType.Kakao) {
    ps = payKakaoService;
}

if (payType == PayType.Samsung) {
    ps = paySamsungService;
}

if (payType == PayType.Naver) {
    ps = payNaverService;
}

ps.pay();

아님 뭐 메서드 내 필드를 이용해서 이렇게 바꿀까? 이것도 뭐 크게 다른건 없다고 느끼는데,
그렇다고 싱글톤 기반의 빈에서 클래스 필드를 맘대로 교체할 수도 없는 노릇이고,
 
좀 더 고차원 레이어에 영향이 안가는 방법의 코드를 작성 할 수는 없을까?
 
있다, Enum과 Provider를 사용하면 된다.
Spring Container는 등록된 모든 Bean에 대한 제어가 가능하다.
일단 등록되어 있으니 가져오는 것도 가능하다.
구조적으로는 위에 if문을 사용하는 것과 차이가 없지만,
 
적어도 인터페이스를 호출 하는 부분을 타입이나, 유형 때문에 바꿀 일은 없을 거다.
 
예시 코드 들어간다.
 
참고로 제 코드가 좋은 코드는 아닙니다용..제발...
 

    public interface PayService {

        void pay();
    }

    private class PayKakaoService implements PayService {

        @Override
        public void pay() {

        }
    }

    private class PaySamsungService implements PayService {

        @Override
        public void pay() {

        }
    }

    private class PayNaverService implements PayService {

        @Override
        public void pay() {

        }
    }

//    private PayService payKakaoService;
//    private PayService paySamsungService;
//    private PayService payNaverService;

    @Getter
    @RequiredArgsConstructor
    public enum PayType {
        Kakao(PayKakaoService.class), Samsung(PaySamsungService.class), Naver(
            PayNaverService.class);

        private final Class<? extends PayService> clazz;
    }

    public class PayServiceProvider {

        private static ApplicationContext applicationContext;
        private static Map<PayType, Class<? extends PayService>> payServiceMap = new HashMap<>();

        static {
            for (PayType payType : PayType.values()) {
                payServiceMap.put(payType, payType.getClazz());
            }
        }

        public static PayService service(PayType payType) throws Exception {
            Class<? extends PayService> payServiceClass = payServiceMap.get(payType);

            if (payServiceClass == null) {
                throw new Exception();
            }

            return applicationContext.getBean(payServiceClass);
        }
    }

    @PostMapping("/payment")
    public ResponseEntity<?> pay(PayType payType) throws Exception {

//        PayService ps = null;
//
//        if (payType == PayType.Kakao) {
//            ps = payKakaoService;
//        }
//
//        if (payType == PayType.Samsung) {
//            ps = paySamsungService;
//        }
//
//        if (payType == PayType.Naver) {
//            ps = payNaverService;
//        }
//
//        ps.pay();

        PayServiceProvider.service(payType).pay();

        return ResponseEntity.ok().build();
    }

    // 실행
    void callPayTest() throws Exception {
        this.pay(PayType.Samsung);
    }

일단, 해당 코드는 provider에서 사용하는 applicationContext에 대한 주입이 없긴하다.
요건 이제 @PostConstruct를 사용해서 주입하면 되긴합니다요.
물론 주입하는 함수도 provider에 static으로 만들어 줘야 합니다.
(대충 읽고 코드만 복붙하면 안되게 하려는 함정...)
 
대강 저런식이면, 앞으로 앱쁠페이든, 티머니든 추가되면 Enum과 구현체의 추가만으로 솔리드 뭐시기를 위배하지 않으면서 해결가능하지 않을까?
 
사실 Provider 구현하기 귀찮다.
유지 보수 측면에서는 좋긴하나, 처음 구현할때 걍 if else 쓰는게 백배 편하다.
시간 비용을 초기에 쓸거냐 나중에 쓸거냐 차이긴한데,
 
아래는 좀 더 편한 방법

private List<PayService> payServices;

@PostMapping("/payment")
public ResponseEntity<?> pay(PayType payType) throws Exception {

    payServices.stream().filter(payService -> payService.check(payType)).findFirst()
        .ifPresent(PayService::pay);
    return ResponseEntity.ok().build();
}

 
스프링은 주입시 배열로 필드를 선언하면 해당 인터페이스에 해당하는 빈들을 모두 가져 오는데요, 이걸 몰랐던건 아닌데, 이렇게 응용할 생각은 못 했네요. 멍청이...
 
아는분이 알려주신건데 아래 링크 통해 배우셨대요!
https://youtu.be/3MTf43_RcVM

 

디스크 컨트롤러

하드디스크는 한 번에 하나의 작업만 수행할 수 있습니다. 디스크 컨트롤러를 구현하는 방법은 여러 가지가 있습니다. 가장 일반적인 방법은 요청이 들어온 순서대로 처리하는 것입니다.

예를들어

- 0ms 시점에 3ms가 소요되는 A작업 요청 - 1ms 시점에 9ms가 소요되는 B작업 요청 - 2ms 시점에 6ms가 소요되는 C작업 요청

와 같은 요청이 들어왔습니다. 이를 그림으로 표현하면 아래와 같습니다.

한 번에 하나의 요청만을 수행할 수 있기 때문에 각각의 작업을 요청받은 순서대로 처리하면 다음과 같이 처리 됩니다.

- A: 3ms 시점에 작업 완료 (요청에서 종료까지 : 3ms) - B: 1ms부터 대기하다가, 3ms 시점에 작업을 시작해서 12ms 시점에 작업 완료(요청에서 종료까지 : 11ms) - C: 2ms부터 대기하다가, 12ms 시점에 작업을 시작해서 18ms 시점에 작업 완료(요청에서 종료까지 : 16ms)

이 때 각 작업의 요청부터 종료까지 걸린 시간의 평균은 10ms(= (3 + 11 + 16) / 3)가 됩니다.

하지만 A → C → B 순서대로 처리하면

- A: 3ms 시점에 작업 완료(요청에서 종료까지 : 3ms) - C: 2ms부터 대기하다가, 3ms 시점에 작업을 시작해서 9ms 시점에 작업 완료(요청에서 종료까지 : 7ms) - B: 1ms부터 대기하다가, 9ms 시점에 작업을 시작해서 18ms 시점에 작업 완료(요청에서 종료까지 : 17ms)

이렇게 A → C → B의 순서로 처리하면 각 작업의 요청부터 종료까지 걸린 시간의 평균은 9ms(= (3 + 7 + 17) / 3)가 됩니다.

각 작업에 대해 [작업이 요청되는 시점, 작업의 소요시간]을 담은 2차원 배열 jobs가 매개변수로 주어질 때, 작업의 요청부터 종료까지 걸린 시간의 평균을 가장 줄이는 방법(포인트3)으로 처리하면 평균이 얼마가 되는지 return 하도록 solution 함수를 작성해주세요. (단, 소수점 이하의 수는 버립니다) 포인트1

제한 사항

  • jobs의 길이는 1 이상 500 이하입니다. 
  • jobs의 각 행은 하나의 작업에 대한 [작업이 요청되는 시점, 작업의 소요시간] 입니다.
  • 각 작업에 대해 작업이 요청되는 시간은 0 이상 1,000 이하입니다.
  • 각 작업에 대해 작업의 소요시간은 1 이상 1,000 이하입니다.
  • 하드디스크가 작업을 수행하고 있지 않을 때에는 먼저 요청이 들어온 작업부터 처리합니다. 포인트2 

 

입출력 예

jobsreturn

[[0, 3], [1, 9], [2, 6]] 9

입출력 예 설명

문제에 주어진 예와 같습니다.

  • 0ms 시점에 3ms 걸리는 작업 요청이 들어옵니다.
  • 1ms 시점에 9ms 걸리는 작업 요청이 들어옵니다.
  • 2ms 시점에 6ms 걸리는 작업 요청이 들어옵니다.

프로그래머스 힙 디스크 컨트롤러 레벨3의 문제.

저는 1년차 같은 10년차 개발자라 그런지...

이 문제 시간제한 있는 상황이라면 저는 무조건 탈락할것 같네요.

문제가 길어서 이해하기도 쉽지 않을뿐더러(책을 많이 읽다보니, 글 읽을때 대각선으로 속독하는 버릇이 있어서...ㅠㅠ)

시간제한 때문에 긴장도 해서 뇌근육이 굳을지도?!

 

일단 위는 복붙, 그 중 포인트는 '소수점 이하의 수는 버립니다'

 

포인트1 : 이 부분이 눈에 안뛰어서 코드를 짜고서 어 뭐지 왜 안대지? 될 코드인데...한참 고민한...

포인트2 : 디스크가 작업을 안하면 먼저 요청이 온 애들을 넣는다라...2초에 작업이 끝나서 5초에 작업이 1개만 들어왔으면 모를까, 3개가 들어왔다면 걔중에서도 선별을 해줘야 하겠죠?

포인트3 : 동시간 요청에 1초 간격의 작업 3개가 들어왔다고 가정!

{{0, 3}, {0, 4}, {0, 5}}

작업시간 순 정렬시 3 + 7 + 12, 역순시 5 + 9 + 17

딱 봐도 동시간 요청은 작업시간이 낮은 순으로 정렬해주는게 좋을듯.

 

요 세가지를 생각하며 코딩했지만, 의외로 놓치는 부분이 생겨서...

점프 구간 부분을 생각을 못 해서...좀 헤맨 ㅠ

(저 아래 주석 처리한 부분)

 

 

코드에 주석을 잘 하는편은 아닌데, 

저도 좀 헷갈려서 혹시 모를 보시는분을 위해 배려 차원에서 주석을 했습니다.

계산을 위해 지정해야하는 변수가 좀 헷갈릴뿐, 전체적인 난이도는 그다지 높지 않은듯 합니다.

가장 중요한건 요구사항에 맞게 정렬만 하면 되는일인데, 막상 정렬 하고서도 위 부분땜에 고생했네요.

 

 

package com.test.heap;

import java.util.Arrays;
import java.util.PriorityQueue;

public class Heap2 {

	public static void main(String[] args) {
		
		int[][] arr = {{0,3}, {1,9}, {2,6}};
//		int[][] arr = {{0,3}, {1,3}, {1,3}, {1,3}, {15,3}};
		
		int r = solution(arr);
		
		System.out.println(r);
	}
	
    public static int solution(int[][] jobs) {
        
        PriorityQueue<Total> ts = new PriorityQueue<>();
        PriorityQueue<Wait> ws = new PriorityQueue<>();
        
        // 배열을 우선순위 큐로 변환
        Arrays.stream(jobs).forEach(job -> {
        	Total t = new Total(job);
        	ts.add(t);
        });

        ws.add(new Wait(ts.poll()));
        
        // 흐른시간
        int totTime = 0;
        // sum(각 프로세스별 처리시간 = 기다린시간 + 일한시간) 
        int totProcessTime = 0;
        while(!ws.isEmpty()) {
        	Wait w = ws.poll();
        	
        	// 기다린시간
        	int waitTime = 0;
        	// 점프한시간(흐른 시간을 위해)
        	int jumptime = 0;
        	
        	
        	if(w.getInterruptTIme() > totTime) { // 요청시간이 흐른 시간보다 미래인 경우
        		waitTime = 0; // 대기는 0보다 작을 수 없으니까
        		jumptime = w.getInterruptTIme() - totTime; // 흐른시간을 계산하기 위해 미래를 현재로 만들기 위한 점프 구간
        	}else { // 대기를 0초 이상 한 경우
        		waitTime = totTime - w.getInterruptTIme();
        	}
        	
        	// 처리시간 = 기다린시간 + 일한시간 
        	int processTime = waitTime + w.getWorkingTime();
        	
        	// 흐른시간 = 기존흐른시간 + 일한시간 + 점프한시간
        	totTime += w.getWorkingTime() + jumptime;
        	
        	totProcessTime += processTime;
        	
    		while((!ts.isEmpty() && ts.peek().getInterruptTIme() <= totTime) // 총대기열이 있고, 흐른시간안에(처리하는동안 or 과거에) 요청이 온 경우가 있을때 
    				|| !ts.isEmpty() && ws.isEmpty()) { // 처리대기열이 비었는데, 총대기열이 남은 경우 (미래작업이 있는 경우)
    			
    			ws.add(new Wait(ts.poll()));
    			
    		}
        	
        }
        
//        return (int)Math.round((double)totProcessTime/jobs.length);
        return totProcessTime/jobs.length;
        
//        int answer = 0;
//        
//        PriorityQueue<Total> ts = new PriorityQueue<>();
//        
//        PriorityQueue<Wait> ws = new PriorityQueue<>();
//        
//        Arrays.stream(jobs).forEach(job -> {
//        	Total t = new Total(job);
//        	ts.add(t);
//        });
//
//        ws.add(new Wait(ts.poll()));
//        
//        
//        int totTime = 0;
//        
//        int lastEndTime = 0;
//        while(!ts.isEmpty() || !ws.isEmpty()) {
//        	Wait w = ws.peek();
//        	
//        	int waitTime = lastEndTime - w.getInterruptTIme();
//        	waitTime = waitTime < 0 ? 0 : waitTime;
//        	int processTime = waitTime + w.getWorkingTime();
//        	
//        	if(lastEndTime > totTime) {
//        		lastEndTime += w.getWorkingTime();
//        		ws.remove();
//        		answer += processTime;
//        	}
//        	
//    		while(!ts.isEmpty() && ts.peek().getInterruptTIme() <= totTime) {
//    			ws.add(new Wait(ts.poll()));
//    		}
//    		totTime++;
//        	
//        }
//        
//        
//        return answer/jobs.length;    
    }
    
}

// 전체대기열
class Total implements Comparable<Total>{

	private int interruptTIme;
	
	private int workingTime;
	
	Total (int[] job){
		this.interruptTIme = job[0];
		this.workingTime = job[1];
	}
	

	public int getInterruptTIme() {
		return interruptTIme;
	}

	public int getWorkingTime() {
		return workingTime;
	}


	// 1. 들어온시간 2. 일하는시간 
	@Override
	public int compareTo(Total o) {
		
		if(this.getInterruptTIme() == o.getInterruptTIme()) {
			return this.getWorkingTime() - o.getWorkingTime();
		}else {
			return this.getInterruptTIme() - o.getInterruptTIme();
		}
		
	}
}

// 진행대기열
class Wait implements Comparable<Wait>{

	private int interruptTIme;
	
	private int workingTime;
	
	Wait (Total t){
		this.interruptTIme = t.getInterruptTIme();
		this.workingTime = t.getWorkingTime();
	}
	
	public int getInterruptTIme() {
		return interruptTIme;
	}

	public int getWorkingTime() {
		return workingTime;
	}

	// 1. 일하는 시간
	@Override
	public int compareTo(Wait o) {
		return this.getWorkingTime() - o.getWorkingTime();
	}
}

문제 설명

전화번호부에 적힌 전화번호 중, 한 번호가 다른 번호의 접두어인 경우가 있는지 확인하려 합니다.
전화번호가 다음과 같을 경우, 구조대 전화번호는 영석이의 전화번호의 접두사입니다.

  • 구조대 : 119
  • 박준영 : 97 674 223
  • 지영석 : 11 9552 4421

전화번호부에 적힌 전화번호를 담은 배열 phone_book 이 solution 함수의 매개변수로 주어질 때, 어떤 번호가 다른 번호의 접두어인 경우가 있으면 false를 그렇지 않으면 true를 return 하도록 solution 함수를 작성해주세요.

제한 사항

  • phone_book의 길이는 1 이상 1,000,000 이하입니다.
    • 각 전화번호의 길이는 1 이상 20 이하입니다.
    • 같은 전화번호가 중복해서 들어있지 않습니다.

입출력 예제

phone_bookreturn

["119", "97674223", "1195524421"] false
["123","456","789"] true
["12","123","1235","567","88"] false

입출력 예 설명

입출력 예 #1
앞에서 설명한 예와 같습니다.

입출력 예 #2
한 번호가 다른 번호의 접두사인 경우가 없으므로, 답은 true입니다.

입출력 예 #3
첫 번째 전화번호, “12”가 두 번째 전화번호 “123”의 접두사입니다. 따라서 답은 false입니다.


알림

2021년 3월 4일, 테스트 케이스가 변경되었습니다. 이로 인해 이전에 통과하던 코드가 더 이상 통과하지 않을 수 있습니다.

 

다른게 아니라 빨간 글씨가 포인트...ㅎㄷㄷ;;

이것저것 막 해봐도 시간초과 나길래...

구글링도 해봐도 시간초과 나길래...

해시관련된걸 써야하나 싶어서...

 

import java.util.HashMap;
import java.util.HashSet;

class Solution {
    public boolean solution(String[] phone_book) {
        
        HashMap<String, String> m1 = new HashMap<>();
        HashSet<String> t = new HashSet<>();
		
        for(int i=0; i<phone_book.length; i++) {
            t.add(phone_book[i]);
        }
		
        for ( String source : phone_book) {
            for( int i=1; i<= source.length(); i++) {
				
                if(!source.substring(0,i).equals(source)) {
                    m1.put(source.substring(0,i), source);
                }
            }
        }
		
        for(String s1 : t) {
            if(m1.containsKey(s1)) {
                return false;
            }
        } 
        
        return true;
    }
}

 

루프가 많아서 수행시간은 비록 오래걸리지만,

일단 해시를 사용하기만하고 작성하자 했는데, 이게 통과되네...ㅡ_ㅡ;

+ Recent posts