스프링의 정수는 엔터프라이즈 서비스 기능을 POJO에 제공하는 것

Professional SPring FrameworkRod Johnson 외 4명
  • 스프링의 주요 기술인 Ioc/DI, AOP와 PSA(Portable Service Abstraction)는 애플리케이션을 POJO로 개발할 수 있게 해주는 가능 기술이라고 불린다

POJO란

  • Plain Old Java Object의 약자
  • 직역하면 평범한 오래된 자바 객체다

POJO의 조건

  1. 특정 규약에 종속되지 않는다.
    • 객체지향 설계의 자유로운 적용이 가능한 오브젝트여야만 POJO라고 불릴 수 있다.
  2. 특정 환경에 종속되지 않는다
    • 환경에 독립적이어야 한다.
    • 특히 비즈니스 로직을 담고 있는 POJO 클래스는 웹이라는 환경정보나 웹 기술을 담고 있는 클래스나 인터페이스를 사용해서는 안된다.
      • 설령 나중에 웹 컨트롤러와 연결돼서 사용될 것이 뻔하도 할지라도
    • 비즈니스 로직을 담은 코드에 HttpServletRequest나 HttpSession, 캐시와 관련된 API가 등장하거나 웹 프레임워ㅏ크의 클래스를 직접 이용하는 부분이 있다면 그것은 진정한 POJO라고 볼 수 없다.

진정한 POJO란 객체지향적인 원리에 충실하면서, 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트를 말한다

POJO의 장점

  1. 특정한 기술과 환겨엥 종속디지 않는 오브젝트는 그만큼 깔끔한 코드가 될 수 있다.
  2. POJO로 개발된 코드는 자동화된 테스트에 매우 유리하다.
  3. 객체지향적인 설계를 자유롭게 적용할 수 있다.

POJO 프레임워크

  • POJO 프로그래밍이 가능하도록 기술적인 기반을 제공하는 프레임워크
  • 스프링 프레임워크와 하이버네이트 등

POJO를 가능하게 하는 스프링의 기술

  1. Ioc/DI
  2. AOP
  3. PSA

제어의 역전(Ioc) / 의존관계 주입(DI)

  • 왜 두개의 오브젝트를 분리해서 만들고, 인터페이스를 두고 느슨하게 연결한 뒤, 실제 사용할 대상은 DI를 통해 외부에서 지정하는 걸까?
  • 이렇게 DI 방식으로 하는 것이 직접 사용할 오브젝트를 new 키워드로 생성해서 사용하는 강한 결합을 쓰는 방법보다 나은 점은 무엇일까?

= 유연한 확장이 가능하게 하기 위해

  • A->B라는 의존관계가 있을 떄 확장은 B가 자유롭게 변경될 수 있음을 의미
  • 이는 B가 변경되어도 A는 아무 영향이 없고 그대로 유지 가능
    B 관점에선 유연한 확장이고, A 관점에서는 변경 없이 재사용이 가능함
  • 개방 폐쇄 원칙(OCP)라는 객체지향 설계 원칙으로 설명할 수 있음

애스펙트 지향 프로그래밍(AOP)

  • 객체지향 기술만으로는 애플리케이션의 요구조건과 기술적인 난해함을 모두 해결하는데 한계가 있음
  • 이를 도와주는 보조적인 프로그래밍 기술이 AOP
  • 스프링의 AOP는 스프링이 POJO 프로그래밍을 지원하려는 그 핵심 목적을 위해 중요한 역할을 하고 있음

포터블 서비스 추상화(PSA)

  • POJO로 개발된 코드는 특정 환경이나 구현 방식에 종속적이지 않아야 함
  • 환경과 세부 기술의 변화에 관계없이 일관된 방식으로 기술에 접근 할 수 있게 해주는게 PSA
  • 단순히 구체적인 기술에 종속되지 않게 하기 위해서 말고 테스트가 어렵게 만들어진 API나 설정을 통해 주요 기느을 외부에서 제어하게 만들고 싶을 때도 이용할 수 있음

댓글 공유

문제

문제에 오류가 있는 것으로 판단됌

https://programmers.co.kr/learn/courses/30/lessons/42860


코드

아래 코드는 통과되지 못했음

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
private static final int A_ASCII = 65;

public int solution(String name) {
char[] toChar = name.toCharArray();

int answer = getUpDownCount(toChar);

int[] serialIndex = getLongestSerialIndex(toChar);

int leftRightCursur = getLeftRightCount(serialIndex[0], serialIndex[1], toChar.length);

answer += leftRightCursur;

return answer;
}

private int getUpDownCount(char[] names) {
int count = 0;
for (char c : names) {
int upDownCurour = c - A_ASCII;
if (upDownCurour > 12) {
upDownCurour = 26 - upDownCurour;
}

count += upDownCurour;
}

return count;
}

private int[] getLongestSerialIndex(char[] array) {
List<Serial> list = getSerialList(array);

Serial serial = getMaxCountSerial(list);

int[] result = new int[2];
result[0] = serial.index;
result[1] = serial.index + serial.count;

return result;
}

private List<Serial> getSerialList(char[] array) {
int count = 0;
List<Serial> list = new ArrayList<Serial>();

for (int i = 0; i < array.length; i++) {
if (array[i] != 'A') {
count = 0;
continue;
}

if (count == 0) {
list.add(new Serial(i, 1));
} else {
list.get(list.size()-1).count = (count+1);
}

count++;
}

return list;
}

private Serial getMaxCountSerial(List<Serial> list) {
int max = 0;
Serial serial = new Serial();

for (Serial s : list) {
if (max <= s.count) {
max = s.count;
serial = s;
}
}

return serial;
}

private int getLeftRightCount(int serialIndex, int serialCount, int nameLength) {
int leftRightCursur = 0;

if (serialIndex == 0 && serialCount == 0) {
leftRightCursur = nameLength - 1;
} else {
int left = serialIndex == 0 ? 0 : serialIndex - 1;
int right = nameLength - serialCount;

leftRightCursur = left > right ? (right * 2) + left : (left * 2) + right;
leftRightCursur = leftRightCursur > nameLength - 1 ? nameLength - 1 : leftRightCursur;
}

return leftRightCursur;
}

class Serial {

private int index;
private int count;

public Serial() {}

public Serial(int index, int count) {
this.index = index;
this.count = count;
}
}

흐름

위 코드는 통과되지 못했음

  1. 문자들을 A로 뺀 후 거꾸로 도는게 빠른지 정방향으로 도는게 빠른지 확인해서 upDownCurour 변수에 저장
  2. 문자열에서 A가 제일 길게 연속되는 부분을 찾기 위해 inner class를 하나 만들어서 A가 연속될 때 마다 A가 시작된 index와 연속된 길이를 저장
  3. A가 제일 길게 저장된 시작 index와 끝 index를 return
  4. 문자열 내에 A가 없으면 정방향으로 돌리고
  5. A가 있으면 return된 index들을 가지고 왼쪽과 오른쪽에서 몇 칸씩 이동해야 하는 지 구함
  6. left와 right를 더하고 가장 짧게 가야하므로 둘 중에 짧은 방향을 한번 더 더함
  7. 더 한 값이 문자열의 길이보다 길면 정방향으로 가는게 더 짧으므로 비교해서 확인
  8. 그렇게 구한 값을 더해서 return

위 코드는 통과되지 못했음


테스트 케이스

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
assertEquals(4, test.solution("AAABAA"));
assertEquals(56, test.solution("JEROEN"));
assertEquals(23, test.solution("JAN"));
assertEquals(48, test.solution("CANAAAAANAN"));
assertEquals(8, test.solution("BBBAAB"));
assertEquals(56, test.solution("AAAAACANAAAAANANAAAAAA"));
assertEquals(3, test.solution("BBAAAAA"));
assertEquals(41, test.solution("JJAJAAAAAAAAAJ"));
assertEquals(21, test.solution("AJAJAA"));
assertEquals(7, test.solution("BBAABAA"));
assertEquals(6, test.solution("BBABA"));
assertEquals(10, test.solution("BBAABBB"));
assertEquals(7, test.solution("BBAABAAAA"));
assertEquals(10, test.solution("BBAABAAAAB"));
assertEquals(6, test.solution("ABAAAAAAABA"));
assertEquals(2, test.solution("AAB"));
assertEquals(11, test.solution("AABAAAAAAABBB"));
assertEquals(5, test.solution("ZZZ"));
assertEquals(10, test.solution("BBBBAAAAAB"));
assertEquals(12, test.solution("BBBBAAAABA"));
assertEquals(10, test.solution("ABABAAAAAAABA"));
assertEquals(18, test.solution("BBBBBBBABB"));
assertEquals(5, test.solution("AZAAAZ"));
assertEquals(3, test.solution("AC"));
assertEquals(3, test.solution("BBAAAAA"));
assertEquals(17, test.solution("ABAAABBBBBBB"));

통과된 코드

  • https://by-dev.tistory.com/9
    • 위 주소의 코드는 “AAABAA”인 경우 2를 return
    • 단순히 B까지 이동하는 것만해도 3이 걸리는데 통과 되었다는게 이해가 가지 않음

댓글 공유

스프링의 정의

자바 엔터프라이즈 개발을 편하게 해주는 오플ㄴ소스 경량급 애플리케이션 프레임워크

이일민토비의 스프링

애플리케이션 프레임워크

  • 특정 계층이나, 기술, 업무 분야에 국한되지 않고 애플케이션의 전 영역을 포괄하는 범용적인 프레임워크
  • 애플리케이션 개발의 전 과정을 빠르고 편리하며 효율적으로 진행하는데 목표를 두는 프레임워크
  • 단지 여러 계층의 다향한 기술을 한데 모아뒀기 때문에 그렇게 불리는 게 아님

스프링의 일차적인 존재 목적은 핵심 기술에 담긴 프로그래밍 모델을 일관되게 적용해서 엔터프라이즈 애플리케이션 전 계층과 전 여영ㄱ에 전략과 기능을 제공해줌으로써 애플리케이션을 편리하게 개발하게 해주는 애플리케이션 프레임워크로 사용되는 것

경량급

  • 자체가 가렵다거나 작은 규모의 코드로 이뤄졌다는 뜻이 아님
  • 불필요하게 무겁지 않다는 의미
  • EJB에 비해 단순한 개발툴과 기본적인 개발환경으로도 엔터프라이즈 개발에서 필요로 하는 주요한 기능을 갖춘 애프리케이션을 개발하기 충분

자바 엔터프라이즈 개발을 편하게

  • 개발자가 복잡하고 실수하기 쉬운 로우레벨 기술에 많은 신경을 쓰지 않게함
  • 애플리케이션의 핵심인 사용자의 요구사항, 즉 비즈니스 로직을 빠르고 효과적으로 구현하는 것
  • 스프링이 제공하는 기술이 아니라 자신이 작성하는 애플리케이션의 로직에 더 많은 관심과 시간을 투자하게 함
  • 초기에 기본 설정과 적용 기술만 선택하고 준비해두면 이후에 개발자가 신경 쓸 일이 없음

결국 스프링의 목적은 스프링을 할용해서 엔터프라이즈 애플리케이션 개발을 편하게 하는 것
그렇다면 스프링은 어떻게 복잡한 엔터프라이즈 개발을 편하게 해주는가?


복잡함의 근본적인 이유

  1. 기술적인 제약조건과 요구사항이 늘어남
  2. 애플리케이션이 구현해야 할 핵심기능인 비즈니스 로직의 복잡함이 증가함
  • 자바 엔터프라이즈 시스템 개발이 어려운 가증 큰 이유는 근본적인 비즈니스 로직과 엔터프라이즈 기술이라는 두 가지 복잡함이 한데 얽혀 있기 때문

해결책

  • 기술의 적용 사실이 코드에 직접 반영되지 않는 비침투적 기술 사용

  • 어딘가에는 기술의 적요에 따라 필요한 작업을 해줘야하겠지만, 애플리케이션 코드 여기저기에 불쑥 등장하거나, 코드의 설꼐와 구현 방식을 제한하지 않는다는 게 비침투적인 기술의 특징

  • 반대로 침투적인 기술은 어떤 기술을 적용했을 떄 그 기술과 관련된 코드나 규약 등이 코드에 등장하는 경우

  • 즉 스프링의 기본적인 전량근 비즈니스 로직을 담은 애플리케이션 코드와 엔터프라이즈 기술을 처리하는 코드를 분리시키는 것

기술적 복잡함을 상대하는 전략

서비스 추상화

  • 일관성 없는 기술과 서버환경의 변화에 대한 스프링의 공략 방법은 서비스 추상화
  • 추상화를 통해 로우레벨의 기술 구현 부분과 기술을 사용하는 인터페이스를 분리하고, 환경과 세부기술에 독립적인 접근 인터페이스를 제공하는 것이 가장 좋은 해결책

AOP

  • 비즈니스 로직 전후로 경계가 설정돼야하는 트랜잭션
  • 비즈니스 로직에 대한 보안 적용
  • 계층 사이에 주고받는 데이터와 예외의 일괄 변환이나 로징이나 감사 기능 등
  • 이런 기술과 비즈니스 로직의 혼재로 발생하는 복잡함을 해결하기 위한 스프링의 접근 방법이 AOP

비즈니스와 애플리케이션 로직의 복잡함을 상대하는 전략

객체지향과 DI

  • 기술적인 복잡함을 효과적으로 다루게 해주는 기법은 모두 DI를 바탕으로 함
  • 서비스 추상화, 템플릿/콜백, AOP 등
  • 그리고 DI는 객체지향 프로그래밍 기법일 뿐
  • DI를 적용해서 오브젝트를 분리하고, 인터페이스를 동비하고, DI로 관계를 연결
  • 결국 DI는 좋은 오브젝트 설계의 결과물이기도 하지만, 반대로 DI를 적용하다보면 객체지향 설계의 원칙을 잘 따르고 그 장점을 살린 설계가 나올 수 있음

결국 스프링의 기술과 전략은 객체지향이라는 자바 언어가 가진 강력한 도구를 극대화해서 사용할 수 있도록 돕는 것 이고 스프링은 단지 거들 뿐이다.

댓글 공유

문제

https://programmers.co.kr/learn/courses/30/lessons/42885


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public int solution(int[] people, int limit) {
Arrays.sort(people);

int left = 0;
int right = people.length - 1;

int answer = 0;
while (left <= right) {
int sum = people[left] + people[right];

if (sum <= limit) {
++left;
}

++answer;
--right;
}

return answer;
}

흐름

  1. 배열을 정렬

  2. 배열 내에서 제일 작은 값이랑 제일 큰 값을 더해서 limit이 넘어가는 지 확인

  3. 넘어가면 제일 작은 값을 증가

  4. 안 넘어가면 제일 큰 값 혼자 빠져야하므로 answer을 증가시키고 right를 감소 시켜서 인덱스를 한칸 땡김

  5. right가 left 보다 작아질 때 까지 반복
    ex) [50,50,70,80] 인 경우 left = 0, right = 4 인데 위 코드르 반복하면 right가 점점 줄어들어 left와 만날 때 끝


결과

정확성 테스트

번호 속도
테스트 1 통과 (1.92ms, 52.7MB)
테스트 2 통과 (1.56ms, 51.4MB)
테스트 3 통과 (1.66ms, 51MB)
테스트 4 통과 (2.02ms, 50.7MB)
테스트 5 통과 (1.42ms, 53MB)
테스트 6 통과 (1.37ms, 50.9MB)
테스트 7 통과 (1.46ms, 53.1MB)
테스트 8 통과 (0.95ms, 52.6MB)
테스트 9 통과 (1.15ms, 52.5MB)
테스트 10 통과 (2.01ms, 52.4MB)
테스트 11 통과 (1.68ms, 52.7MB)
테스트 12 통과 (1.78ms, 52.7MB)
테스트 13 통과 (2.07ms, 52.5MB)
테스트 14 통과 (1.46ms, 50.5MB)
테스트 15 통과 (1.22ms, 50.1MB)

효율성 테스트

번호 속도
테스트 1 통과 (11.43ms, 56.1MB)
테스트 2 통과 (11.63ms, 55.7MB)
테스트 3 통과 (11.88ms, 53.7MB)
테스트 4 통과 (8.84ms, 56.3MB)
테스트 5 통과 (11.08ms, 55.7MB)

테스트 케이스

1
2
3
4
5
6
assertEquals(3, test.solution(new int[] {70, 50, 80, 50}, 100));
assertEquals(3, test.solution(new int[] {70, 80, 50}, 100));
assertEquals(2, test.solution(new int[] {40, 40, 80}, 160));
assertEquals(2, test.solution(new int[] {20, 50, 50, 80}, 100));
assertEquals(5, test.solution(new int[] {40,50,60,70,80,90}, 100));
assertEquals(2, test.solution(new int[] {40,40,40}, 100));

댓글 공유

문제

https://programmers.co.kr/learn/courses/30/lessons/42883?language=java


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public String solution(String number, int k) {
char[] toCharArray = number.toCharArray();

StringBuilder answer = new StringBuilder();

int idx = 0;
for(int i = 0; i < number.length() - k; i++) {
int max = 0;

for(int j = idx; j <= k + i; j++) {
int ch = toCharArray[j] - '0';
if(max < ch) {
max = ch;
idx = j + 1;
}
}

answer.append(max);
}

return answer.toString();
}

흐름

  1. 문자열에서 k 만큼 빼야하니 당연히 문자열 length - k 만큼 반복
  2. 가장 큰 수의 인덱스를 구해서 그 인덱스부터 한 칸씩 밀려야 하니 k + i 한 값 까지 반복
  3. 가장 큰 수의 인덱스부터 반복해야하니 범위 내에서 가장 큰 수를 구해서 그 수의 인덱스를 저장하고 여기서 구한 인덱스를 2번에서 사용
  4. 가장 큰 수를 저장하고 반복이 끝나면 리턴

테스트 케이스

1
2
3
4
5
6
7
assertEquals("23", test.solution("123", 2));
assertEquals("34", test.solution("1234", 2));
assertEquals("94", test.solution("1924", 2));
assertEquals("3234", test.solution("1231234", 3));
assertEquals("775841", test.solution("4177252841", 4));
assertEquals("9", test.solution("9999999999", 9));
assertEquals("93231357719", test.solution("9312131357719", 5));

참고 사이트

댓글 공유

문제

https://programmers.co.kr/learn/courses/30/lessons/42578


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public int solution(String[][] clothes) {
Map<String, Integer> map = new HashMap<String, Integer>();

for (String[] s : clothes) {
int value = 1;
String key = s[1];
if (map.containsKey(key)) {
value = map.get(key);
}

map.put(key, ++value);
}

int answer = 1;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
answer *= entry.getValue();
}

return answer - 1;
}

흐름

  1. [[“yellow_hat”, “headgear”], [“blue_sunglasses”, “eyewear”], [“green_turban”, “headgear”]], clothes안에 이런 식으로 옷이름, 종류 로 저장되어 있으므로 chothes 크기 만큼 반복하면서 옷 종류를 꺼냄

  2. 옷 종류를 key로 하는 Map이 있는 지 확인

  3. 있으면 값을 증겨 시켜야 하므로 value를 꺼내옴

  4. ++해서 값을 증가 시켜서 저장하고, 없으면 해당 key로 새로 저장

  5. 순열과 조합 공식을 바탕으로 value를 모두 곱함

  6. 문제에서 최소 1 개의 옷은 입는다고 하였으므로, 옷을 모두 안입는 경우를 제외하기 위해 -1을 함

조합 공식

Every interaction is both precious and an opportunity to delight.

서로 다른 n개의 원소에서 순서에 상관없이 r개를 뽑을 때, 이를 n개에서 r개를 택하는 조합이라고 한다. 이 조합은 순열과 다른 개념으로 순서 차이가 중요하다.

$$ nCr = nPr / r! = n! / (n-r)!r! $$

  • 이 공식으로 위 문제를 풀어보면 헤어 2개, 안경 1개 이므로
  • 헤어 2개에서는 입을경우, 안 입을 경우 2 가지이고 이를 수식으로 표현하면
  • 0개를 뽑는 경우 = $$ 2C0 = 2P0 / 0! = 2! / (2-0)!0! = 2 / 2 = 1 $$
  • 이 되고, 나머지의 경우도 계산하면 $$ 1 + 2 = 3 $$ 이 된다.
  • 안경도 마찬가지로 입을 경우, 안 입을 경우 2 가지로
  • $$ 1C0 + 1C1 = 1 + 1 = 2 $$ 가 되고
  • 마지막으로 둘 다 안 입는 경우는 없으므로 -1을 하면
  • $$ 3 * 2 - 1 = 5 $$가 된다

다른 분의 코드

1
2
3
4
5
6
7
public int solution(String[][] clothes) {
return Arrays.stream(clothes)
.collect(groupingBy(p -> p[1], mapping(p -> p[0], counting())))
.values()
.stream()
.collect(reducing(1L, (x, y) -> x * (y + 1))).intValue() - 1;
}
  • 람다식 대박

결과

1번

번호 속도
테스트 1 통과 (0.93ms, 50.5MB)
테스트 2 통과 (0.85ms, 51.9MB)
테스트 3 통과 (0.86ms, 51.9MB)
테스트 4 통과 (0.93ms, 52.8MB)
테스트 5 통과 (0.80ms, 52MB)
테스트 6 통과 (0.88ms, 50.5MB)
테스트 7 통과 (0.84ms, 52.2MB)
테스트 8 통과 (0.85ms, 52.8MB)
테스트 9 통과 (0.86ms, 51.9MB)
테스트 10 통과 (0.87ms, 52.2MB)
테스트 11 통과 (0.89ms, 52.4MB)
테스트 12 통과 (0.82ms, 51.9MB)
테스트 13 통과 (0.81ms, 52.3MB)
테스트 14 통과 (0.75ms, 51.9MB)
테스트 15 통과 (0.82ms, 50.5MB)
테스트 16 통과 (0.91ms, 50.5MB)
테스트 17 통과 (0.90ms, 52.5MB)
테스트 18 통과 (0.88ms, 52.1MB)
테스트 19 통과 (0.88ms, 51.8MB)
테스트 20 통과 (0.90ms, 52.4MB)
테스트 21 통과 (0.80ms, 52.9MB)
테스트 22 통과 (0.86ms, 51.6MB)
테스트 23 통과 (0.80ms, 50.7MB)
테스트 24 통과 (0.90ms, 50.3MB)
테스트 25 통과 (0.90ms, 52.3MB)
테스트 26 통과 (0.93ms, 52.1MB)
테스트 27 통과 (0.86ms, 52.3MB)
테스트 28 통과 (0.90ms, 50.1MB)

2번

번호 속도
테스트 1 통과 (14.41ms, 53.2MB)
테스트 2 통과 (12.19ms, 52.8MB)
테스트 3 통과 (12.05ms, 52.9MB)
테스트 4 통과 (13.03ms, 50.9MB)
테스트 5 통과 (14.21ms, 50.9MB)
테스트 6 통과 (12.85ms, 51MB)
테스트 7 통과 (13.19ms, 50.9MB)
테스트 8 통과 (13.32ms, 53.6MB)
테스트 9 통과 (13.37ms, 53.1MB)
테스트 10 통과 (12.58ms, 53.2MB)
테스트 11 통과 (12.34ms, 52.7MB)
테스트 12 통과 (13.69ms, 52.4MB)
테스트 13 통과 (13.19ms, 52.7MB)
테스트 14 통과 (12.48ms, 51.1MB)
테스트 15 통과 (11.82ms, 52.5MB)
테스트 16 통과 (12.63ms, 52.7MB)
테스트 17 통과 (13.37ms, 53.1MB)
테스트 18 통과 (13.54ms, 53.1MB)
테스트 19 통과 (13.20ms, 50.7MB)
테스트 20 통과 (13.11ms, 52.7MB)
테스트 21 통과 (12.80ms, 52.6MB)
테스트 22 통과 (13.48ms, 53.2MB)
테스트 23 통과 (12.59ms, 52.5MB)
테스트 24 통과 (13.71ms, 50.9MB)
테스트 25 통과 (12.94ms, 52.4MB)
테스트 26 통과 (13.67ms, 50.9MB)
테스트 27 통과 (13.75ms, 53MB)
테스트 28 통과 (13.04ms, 51.7MB)

테스트 케이스

1
2
assertEquals(5, test.solution(new String[][] {{"yellow_hat", "headgear"}, {"blue_sunglasses", "eyewear"}, {"green_turban", "headgear"}}));
assertEquals(3, test.solution(new String[][] {{"crow_mask", "face"}, {"blue_sunglasses", "face"}, {"green_turban", "face"}}));

참고 사이트

댓글 공유

AOP란

  • AOP는 Ioc, DI, 서비스 추상화와 더불어 스프링의 3대 기반 기술
  • 선언적 트랜잭션 기능이 대표적

트랜잭션 코드의 분리

  • 비즈니스 로직 전 후에 로직과 전혀 상관없는 트랜잭션 경계를 설정해야 하기 때문에 필연적으로 코드가 지저분해짐

메서드 분리를 이용해 분리

  • 비즈니스 로직을 담당하는 코드만 따로 메서드로 추출
  • 하지만 여전히 비즈니스 로직이 아닌 보일 필요가 없는 트랜잭션 코드가 남아있음
  • 그렇다면 클래스에서 빼서 아예 눈에 안띄게 해버리자

DI를 이용한 클래스의 분리

  • DI의 기본 아이디어는 실제 사용할 오브젝트의 클래스 정체는 감춘 채 인터페이스를 통해 간접으로 접근하는 것

  • 그 덕분에 구현 클래스는 얼마든지 외부에서 변경 가능

  • UserService 인터페이스를 상속 받는 클래스를 2 가지로 나눔

    1. ServiceImpl(비즈니스 담당)
    2. ServiceTx(트랜잭션 담당)
  • Impl에선 비즈니스 로직을 담당하는 메서드만 남겨두고 Tx에선 userService를 구현한 다른 오브젝트를 주입 받고 트랜잭션 안에서 동작할 수 있도록 메서드 안에서 트랜잭션 경계를 설정

1
2
3
4
5
6
7
public void upgardeLevels() {
트랜잭션 스타트();

주입받은 userService를 구현한 오브젝트(Impl).upgrardeLevels();

commit() or rollback();
}

DI를 이용한 트랜잭션 경계설정 코드 분리의 장점

  1. 비즈니스 로직만 담긴 Impl 클래스에선 코드을 작성 시 트랜잭션 같은 비즈니스 로직과 관계 없는 부분에 대해 전혀 신경 쓰지 않아도 됌
  2. 테스트가 쉬워짐

다시 돌아와 AOP: 애스펙트(aspect) 지향 프로그래밍

  • 부가기능 모듈화 작업
  • 핵심기능에 부가되어 의미를 갖는 특별한 모듈
  • 에스펙트는 부가될 기능을 정의한 코드인 어드바이스, 어드바이스를 어디에 적용할지를 결정하는 __포인터컷__을 함께 갖고 있음
  • 위에서 트랜잭션 코드는 비즈니스 로직에 상관없는 기능인데 핵심기능에 침투해 들어가면서 설계와 코드가 모두 지저분해짐
  • 이런 부가기능 코든는 여기저기 메서드에 마구 흩어져서 나타나고 코드는 중복됌
  • 런타임 시에는 각 부가기능 에스펙트는 자기가 필요한 위치에 다이내믹하게 참여
  • 하지만 설계와 개발은 다른 특성을 띤 에스펙트들을 독립적인 관점으로 작성
  • AOP는 에스펙트를 분리함으로써 핵심기능을 설계하고 구현할 때 객체지향적인 가치를 지킬 수 있도록 도와주는 것
  • 애플리케이션을 특정한 관점을 기준으로 바라볼 수 있게 해준다는 의미에서 관점 지향 프로그래밍이라고도 함

프록시를 이용한 AOP

  • 스프링은 다양한 기술을 조합해 AOP를 지원하는데 가장 핵심은 프록시를 이용한다는 것
  • 프록시로 만들어서 DI로 연결된 빈 사이에 적용해 타깃의 메서드를 호출 과정에 참여 해서 부가기능을 제공
  • 스프링 AOP의 부가기능을 담은 어드바이스가 적용되는 대상은 오브젝트의 메서드

바이트코드 생성과 조작을 통한 AOP

  • AspectJ는 컴파일된 타깃의 클래스 파일 자체를 수정하거나 JVM에 로딩되는 시점을 가로채서 바이트코드를 조작하는 방식을 사용
  • 이 때 장점은,
    1. DI 컨테이너의 도움을 받아서 자동 프록시 생성방식을 사용하지 않아도 AOP 적용 가능하기 때문에 컨테이너가 없는 환경에서도 AOP의 적용 가능
    2. 프록시 방식보다 훨씬 강력하고 유연한 AOP가 가능, 프록시는 부가기능을 부여할 대상은 클라이언트가 호출 할 떄 사용하는 메서드로 제한되지만, 바이트코드를 직접 조작하기 떄문에 오브젝트의 생성, 필드 값으 조회와 조작, static 초기화등의 다양한 작업에 부가기능 부여 가능

AOP 용어 정리

  • 타깃
    • 부가기능을 부여할 대상
  • 어드바이스
    • 타깃에게 제공할 부가기능을 담을 모듈
  • 조인 포인트
    • 어드바이스가 적용될 수 잇는 위치
    • 스프링 AOP의 조인 포인트는 메서드의 실행단계
    • 타깃 오브젝트가 구현한 인터페이스의 모든 메서드는 조인 포인트가 됌
  • 포인트컷
    • 어드바이스를 적용할 조인 포인트를 선별하는 작업 또는 그 기능을 정의한 모듈
    • 메서드의 시그니처를 비교하는 방법을 주로 사용
  • 프록시
    • 클라이언트와 타깃 사이에 투명하게 존재하면서 부가기능을 제공하는 오브젝트
  • 어드바이저
    • 포인트컷과 어드바이스를 하나씩 갖고 있는 오브젝트
    • 어떤 부가기능(어드바이스)를 어디에(포인트 컷) 전달할 것인가를 알고 있는 AOP의 가장 기본이 되는 모듈
  • 애스펙트
    • AOP의 기본 모듈
    • 한 개 또는 그 이상의 포인트 컷과 어드바이스를 조합
    • 보통 싱글톤 형태의 오브젝트

댓글 공유

문제

https://programmers.co.kr/learn/courses/30/lessons/42577


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public boolean solution(String[] phone_book) {
int length = phone_book.length;

for (int i = 0; i < length; i++) {
for (int j = 0; j < length; j++) {
if (i == j) {
continue;
}

if (phone_book[j].startsWith(phone_book[i])) {
return false;
}
}
}
boolean answer = true;
return answer;
}

흐름

  1. 배열을 전부 돌면서 해당 인덱스로 시장하는 단어가 있는 지 확인함


결과

번호 속도
테스트 1 통과 (0.78ms, 52.1MB)
테스트 2 통과 (0.52ms, 50.1MB)
테스트 3 통과 (0.65ms, 52.6MB)
테스트 4 통과 (0.51ms, 54.6MB)
테스트 5 통과 (0.55ms, 52.2MB)
테스트 6 통과 (0.66ms, 52.5MB)
테스트 7 통과 (0.87ms, 50.3MB)
테스트 8 통과 (0.77ms, 52.8MB)
테스트 9 통과 (0.64ms, 52.3MB)
테스트 10 통과 (0.57ms, 50.3MB)
테스트 11 통과 (0.65ms, 52.8MB)
  • 효율성 테스트
번호 속도
테스트 1 통과 (1.96ms, 60.3MB)
테스트 2 통과 (2.14ms, 59.8MB)

테스트 케이스

1
2
3
assertFalse(test.solution(new String[] {"119", "97674223", "1195524421"} ));
assertTrue(test.solution(new String[] {"123","456","789"} ));
assertFalse(test.solution(new String[] {"12","123","1235","567","88"} ));

여담

  • 사실 당연히 시간 초과로 안될 줄 알았는데 넘어가서 놀랐다.
  • 찾아보니 Trie 자료구조로 해결하길 바래서 카테고리가 해쉬 인 듯 싶다.
  • 하지만 쉽게 풀 수 있는 문제를 어렵게 풀 이유가 없으니 이 문제는 이렇게 마무리 하려한다.

댓글 공유

문제

https://programmers.co.kr/learn/courses/30/lessons/42747?language=java


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public int solution(int[] citations) {
int answer = 0;
int n = citations.length;

Arrays.sort(citations);

for (int i = 0; i <= citations[n - 1]; i++) {
int high = 0;

for (int j = 0; j < n; j++) {
if (i <= citations[j]) {
high++;
}

if (i <= high) {
answer = i;
}
}
}

return answer;
}

흐름

  1. 논문을 인용한 횟수가 담긴 배열 citations를 정렬

  2. 가장 많이 인용한 횟수 만큼 반복

  3. 0부터 배열 길이만큼 반복하면서 i 값이 인용 횟수보다 작은 경우 i 보다 인용된 횟수가 많은 것이므로 high 변수 증가

  4. h번 이상 인용된 논문이 h편 이상이여야 하므로 i가 high 보다 작을 경우에만 answer에 저장

  5. i가 high보다 커질 경우 answer에 저장되지 않으므로 answer에 마지막으로 저장된 값이 h


다른 해결방법

코드

1
2
3
4
5
6
7
8
9
10
11
public int solution(int[] citations) {
Arrays.sort(citations);

int max = 0;
for(int i = citations.length-1; i > -1; i--){
int min = (int)Math.min(citations[i], citations.length - i);
if(max < min) max = min;
}

return max;
}

위 코드의 로직

  1. 배열을 정렬 후 배열의 마지막 값과 1 부터 증가 시키면서 citations의 길이 만큼 반복함

  2. 예를 들어 {0, 1, 3, 5, 6} 인 경우, citations.length는 5, i = 5 - 1 이므로 4, citations.length - i는 5 - 4가 되므로 1임 그런고로, 1과 citations[4]인 6과 비교해서 작은 놈을 min에 저장

  3. 위 과정을 반복하면
    min = Min(6,1)
    min = Min(5,2)
    min = Min(3,3)
    min = Min(1,4)
    min = Min(0,5)
    가 되고 max는 max가 min 보다 작을 때 max에 min 값이 저장되므로 max엔 최종적으로 3이 저장

  • 어떻게 유추해서 푼 건지 이해가 안간다 천재인 듯

결과

1번

번호 속도
테스트 1 통과 (11.43ms, 50.3MB)
테스트 2 통과 (14.71ms, 52.4MB)
테스트 3 통과 (15.13ms, 50.7MB)
테스트 4 통과 (13.74ms, 50.1MB)
테스트 5 통과 (16.49ms, 50.6MB)
테스트 6 통과 (13.86ms, 50MB)
테스트 7 통과 (10.08ms, 52.4MB)
테스트 8 통과 (7.45ms, 52.6MB)
테스트 9 통과 (8.26ms, 52.9MB)
테스트 10 통과 (10.29ms, 50MB)
테스트 11 통과 (17.04ms, 50.9MB)
테스트 12 통과 (8.21ms, 52.8MB)
테스트 13 통과 (15.30ms, 52.5MB)
테스트 14 통과 (13.62ms, 52.7MB)
테스트 15 통과 (15.64ms, 50.5MB)
테스트 16 통과 (0.97ms, 54.7MB)

2번

번호 속도
테스트 1 통과 (1.10ms, 52.4MB)
테스트 2 통과 (1.36ms, 50.5MB)
테스트 3 통과 (1.36ms, 50.8MB)
테스트 4 통과 (0.85ms, 51.9MB)
테스트 5 통과 (0.95ms, 52.5MB)
테스트 6 통과 (1.35ms, 52.7MB)
테스트 7 통과 (1.00ms, 50.6MB)
테스트 8 통과 (0.87ms, 50.9MB)
테스트 9 통과 (1.06ms, 50.5MB)
테스트 10 통과 (1.11ms, 52.6MB)
테스트 11 통과 (1.39ms, 52.5MB)
테스트 12 통과 (0.91ms, 52.6MB)
테스트 13 통과 (1.22ms, 50.3MB)
테스트 14 통과 (1.50ms, 52.5MB)
테스트 15 통과 (1.36ms, 52.3MB)
테스트 16 통과 (0.94ms, 52.5MB)
  • 속도도 압도적…

테스트 케이스

1
2
3
4
5
6
7
8
9
10
11
assertEquals(3, test.solution(new int[] {3,0,6,1,5}));
assertEquals(3, test.solution(new int[] {6,5,4,3,1,1,1,1,1}));
assertEquals(3, test.solution(new int[] {0, 1, 1, 1, 1, 3, 3, 4}));
assertEquals(3, test.solution(new int[] {5, 5, 5, 0}));
assertEquals(2, test.solution(new int[] {2,2,2,2,2}));
assertEquals(4, test.solution(new int[] {5, 5, 5, 5}));
assertEquals(5, test.solution(new int[] {5, 5, 5, 5, 5}));
assertEquals(1, test.solution(new int[] {7}));
assertEquals(3, test.solution(new int[] {4, 3, 3, 3, 3}));
assertEquals(0, test.solution(new int[] {0,0,0,0,0,0}));
assertEquals(3, test.solution(new int[] {6, 5, 3, 1, 0}));

댓글 공유

문제

https://programmers.co.kr/learn/courses/30/lessons/42746


코드

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public String solution(int[] numbers) {

List<Numbers> list = new ArrayList<Numbers>();
for (int i = 0; i < numbers.length; i++) {
int number = numbers[i];

char[] str = String.valueOf(number).toCharArray();
StringBuilder fourDigitString = new StringBuilder();

fourDigitString.append(str);
if (str.length < 4) {

for (int j = 0; j < 4 - str.length; j++) {
fourDigitString.append(str[((j) % str.length)]);
}
}

list.add(new Numbers(i, Integer.parseInt(fourDigitString.toString())));
}

Collections.sort(list, new Comparator<Numbers>() {

@Override
public int compare(Numbers o1, Numbers o2) {
return o2.value.compareTo(o1.value);
}
});

StringBuilder sb = new StringBuilder();
int sum = 0;
for (Numbers n : list) {
sum += n.value;
sb.append(numbers[n.postion]);
}

if (sum == 0) {
sb.setLength(0);
sb.append(0);
}

return sb.toString();
}

class Numbers {
public Integer postion;
public Integer value;

public Numbers(Integer postion, Integer value) {
this.postion = postion;
this.value = value;
}
}

흐름

  1. 문제에서 numbers의 원소 값이 1000 이하라고 했으므로 numbers 크기 만큼 돌면서 numbers 안에 값이 4자리가 아닌 경우 자기 숫자를 뒤에 덧붙여서 4자리로 만듬
    • ex) 121 > 1212, 1 > 1111, 23 > 2323, …
  2. 현재 배열의 index와 값을 비교하기 위에 inner class를 하나 만들어서 index와 value를 저장하고 class를 list에 추가
  3. list를 class의 value 값으로 큰 수부터 나오도록 정렬
  4. list를 돌면서 value를 StringBuilder로 붙임
  5. 이 때, 0 0 0 0 일 경우 0 이 되어야 하므로 value들을 모두 더했을 때 값이 0이라면 0 만 붙임
  6. toString() 으로 String형으로 return

다른 분들의 해결방법

다른분의 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public String solution(int[] numbers) {
String answer = "";

List<Integer> list = new ArrayList<>();
for(int i = 0; i < numbers.length; i++) {
list.add(numbers[i]);
}
Collections.sort(list, (a, b) -> {
String as = String.valueOf(a), bs = String.valueOf(b);
return -Integer.compare(Integer.parseInt(as + bs), Integer.parseInt(bs + as));
});
StringBuilder sb = new StringBuilder();
for(Integer i : list) {
sb.append(i);
}
answer = sb.toString();
if(answer.charAt(0) == '0') {
return "0";
}else {
return answer;
}
}

위 코드의 로직

  • list에 numbers array를 그대로 저장하고 list의 저장된 element를 앞뒤로 붙여서 정수로 바꾼 후 비교해서 정렬
  • String을 다루는 부분이 많아서 속도는 살짝 떨어지는 듯

결과

1 번

번호 속도
테스트 1 통과 (139.80ms, 83.2MB)
테스트 2 통과 (88.71ms, 67MB)
테스트 3 통과 (167.96ms, 82.4MB)
테스트 4 통과 (17.95ms, 50.7MB)
테스트 5 통과 (129.09ms, 77.4MB)
테스트 6 통과 (116.84ms, 71.2MB)
테스트 7 통과 (1.44ms, 52.3MB)
테스트 8 통과 (1.38ms, 52.7MB)
테스트 9 통과 (1.20ms, 50.2MB)
테스트 10 통과 (1.46ms, 52.6MB)
테스트 11 통과 (1.65ms, 52.9MB)

2 번

번호 속도
테스트 1 통과 (340.75ms, 105MB)
테스트 2 통과 (236.67ms, 82.1MB)
테스트 3 통과 (350.56ms, 126MB)
테스트 4 통과 (80.72ms, 59.7MB)
테스트 5 통과 (300.18ms, 110MB)
테스트 6 통과 (280.61ms, 87.6MB)
테스트 7 통과 (31.26ms, 57.2MB)
테스트 8 통과 (35.88ms, 53.4MB)
테스트 9 통과 (34.27ms, 55.3MB)
테스트 10 통과 (30.44ms, 55.6MB)
테스트 11 통과 (40.16ms, 55.3MB)

테스트 케이스

1
2
3
4
5
6
7
8
9
assertEquals("6210", test.solution(new int[] {6, 10, 2}));
assertEquals("9534330", test.solution(new int[] {3, 30, 34, 5, 9}));
assertEquals("220200", test.solution(new int[] {2,200,20}));
assertEquals("2200", test.solution(new int[] {2,0,20}));
assertEquals("0", test.solution(new int[] {0,0,0}));
assertEquals("21212", test.solution(new int[] {12, 212}));
assertEquals("21221", test.solution(new int[] {212, 21}));
assertEquals("7000", test.solution(new int[] {0, 0, 70}));
assertEquals("1000000", test.solution(new int[] {0, 0, 0, 1000}));

댓글 공유

Junggu Ji

author.bio


author.job