[ redirect 시 attribute 전달하는방법 ]

redirectAttributes.addFlashAttribute 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping(value="/save.do")
public String save(HttpServletRequest request, RedirectAttributes redirectAttributes, paramVO vo) throws Exception { 
    ...        
    redirectAttributes.addFlashAttribute("param", param);
    return "redirect:/result.do";
}
 
 
@RequestMapping(value="/result.do")
public ModelAndView result(HttpServletRequest request, @ModelAttribute("paramVO") paramVO vo) throws Exception {
    ModelAndView mav = new ModelAndView("bmp/Result");
    ...
    logger.info(vo.getName());
    return mav;
}
cs

 

https://stackoverflow.com/questions/19266427/what-are-ways-for-pass-parameters-from-controller-after-redirect-in-spring-mvc

 

※ addFlashAttribute 와 addAttribute 차이

addAttribute 는 단수개의 파라미터들 (내부적으로 redirect url 뒤에 붙여서(get과 같이) 나가는 방식)

addFlashAttribute 복수개의 파라미터(map , vo 등)

https://bactoria.tistory.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81-addAttribute-addFlashAttribute-%EC%B0%A8%EC%9D%B4%EC%A0%90-RedirectAttributes-rttr-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%ED%8A%B8

 

 

[ redirect, forward 차이 ]

redirect

request, response 를 새로 생성, 새로운 호출 (뒤로가기 가능(A에서 B로 리다이렉트시 B에서 뒤로가기로 A 이동 가능))

ex) 고객센터에 전화를 걸었더니 다른 번호로 연결시켜주어 새로운 곳에 문의사항을 다시 전달해야 하는 상황

 

forward

request, response 를 공유, 기존 호출에 이어서 진행 (뒤로가기 불가(A에서 B로 포워딩시 B에서 뒤로가기로 A를 부른 C로 이동(전전 화면)))

ex) 고객센터에 전화를 걸었더니 담당자가 잘 모르겠다며 옆의 직원에게 상황을 전달하며 수화기를 넘긴 상황

https://doublesprogramming.tistory.com/63

반응형

Queue

FIFO(First In First Out : 선입선출) 형태를 갖는 자료구조.

일상생활에서 Queue와 가장 밀접한 예로 줄을 서는 행위를 들 수 있다. 

보통 줄을 설 때 줄을 가장 먼저 선 사람이 가장 먼저 처리되므로 Queue와 같다.(Queue 라는 단어 자체가 줄을 서다 라는 뜻을 갖는 동사, 대기줄이라는 뜻을 갖는 명사 이다)

 

[java.util.Queue 의 기본적인 사용예]

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
package algo_datastructure.queue.queuebyarray;
 
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
 
public class Main {
    public static void main(String[] args) {
        
        System.out.println("Queue---------");
        Queue<Integer> q = new LinkedList<Integer>();
        q.add(1);
        q.add(2);
        System.out.println(q.peek());
        while(!q.isEmpty()) {
            System.out.print(q.poll() + " ");
        }
        
        System.out.println("\nPriorityQueue (default : ascending)---------");
        Queue<Integer> q2 = new PriorityQueue<Integer>();
        q2.add(1);
        q2.add(3);
        q2.add(2);
        System.out.println(q2.peek());
        while(!q2.isEmpty()) {
            System.out.print(q2.poll() + " ");
        }
        
        System.out.println("\nPriorityQueue (Descending)---------");
        Queue<Integer> q3 = new PriorityQueue<Integer>((o1, o2)->o2-o1);
        q3.add(1);
        q3.add(3);
        q3.add(2);
        System.out.println(q3.peek());
        while(!q3.isEmpty()) {
            System.out.print(q3.poll() + " ");
        }
    }
}
 
cs

[실행결과]

Queue---------
1
1 2
PriorityQueue (default : ascending)---------
1
1 2 3
PriorityQueue (Descending)---------
3
3 2 1

Queue 는 입력된 순서대로 저장 및 출력되는(LinkedList 를 구현체로 사용) Queue 와

입력된 데이터들을 특정 기준에 의해 정렬된 상태로 저장 및 출력하는 PriorityQueue 가 있다.

위와 같이 PriorityQueue는 Comparator 인터페이스를 파라미터로 넘겨 정렬순서를 원하는대로 선택할 수 있다. (위 예제에선 람다로 처리)

Comparator 인터페이스를 넘겨주지 않을 경우, 오름차순 정렬(default)로 처리된다.

 

두가지 queue 모두 공통적으로 아래와 같은 메소드를 갖는다.

add() : queue 에 값을 넣는다. (값 넣기 실패시 exception 을 뱉는다)

offer() : add()와 같이 queue에 값을 넣는다. (값 넣기 실패시 false 를 리턴한다)

peek() : queue 내에서 가장 앞에 위치한 데이터를 꺼낸다.(데이터는 그대로 둔다)

poll() : queue 내에서 가장 앞에 위치한 데이터를 꺼낸다.(데이터를 꺼내고 지운다)

 

[Array로 구현한 Queue]

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
package algo_datastructure.queue.queuebyarray;
 
public class Queue {
    
    private int[] array = new int[100];
int lastIdx = 0;
    
    public void add(int input) {
        array[lastIdx] = input;
        lastIdx++;
    }
    
    public int peek() {
        return array[0];
    }
    
    public int poll() {
        int rs = array[0];
        
        for(int i=1; i<=lastIdx; i++) {
            array[i-1= array[i];
        }
        lastIdx--;
        
        return rs;
    }
    
    public boolean isEmpty() {
        return lastIdx==0?true:false;
    }
}
 
cs

[사용]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package algo_datastructure.queue.queuebyarray;
 
public class Main {
    public static void main(String[] args) {
        
        Queue q = new Queue();
        q.add(1);
        q.add(2);
        q.add(3);
        System.out.println(q.peek());
        while(!q.isEmpty()) {
            System.out.print(q.poll() + " ");
        }
    }
}
 
cs

 

반응형

Stack

LIFO (Last In First Out : 후입선출) 형태를 갖는 자료구조.

임시로 자료를 저장하고 꺼내서 사용하는 임시 버퍼와 같다.

java 메모리구조에서 new 연산자로 생성된 인스턴스 등이 Stack 으로 관리된다.

 

일상생활에서 Stack과 가장 밀접한 예로 장독대를 들 수 있다.

김치를 넣고 꺼낼 때 가장 나중에 넣은 김치를 가장 먼저 꺼내야 하는 구조이므로 Stack과 같다.

 

[java.util.Stack 의 기본적인 사용예]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package algo_datastructure.stack;
 
import java.util.Stack;
 
public class BasicUsageOfStack {
    public static void main(String[] args) {
 
        Stack<Integer> s = new Stack<>();
        s.add(1);
        s.add(2);
        s.add(3);
 
        System.out.println(s.peek());
 
        while(!s.isEmpty()) {
            System.out.println(s.pop());;
        }
        
    }
}
 
cs

 

add로 stack에 값을 넣는다. (push와 같음)

peek으로 stack 내에서 가장 위에 위치한 데이터를 꺼낸다.(데이터는 그대로 둔다)

pop으로 stack 내에서 가장 위에 위치한 데이터를 꺼낸다.(데이터를 꺼내고 지운다)

 

 

[Array로 구현한 Stack]

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
package algo_datastructure.stack.stackbyarray;
 
public class Stack {
    private int[] array = new int[100];
    private int lastIdx = 0;
    
    public Stack () {
    }
    
    public void add(int input) {
        array[lastIdx] = input;
        lastIdx++;
    }
    
    public int pop() {
        lastIdx--;
        int top = array[lastIdx];
        array[lastIdx] = 0;
        
        return top;
    }
    
    public int peek() {
        return array[lastIdx-1];
    }
    
    public boolean isEmpty() {
        return lastIdx==0?true:false;
    }
}
 
cs

[사용]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package algo_datastructure.stack.stackbyarray;
 
public class Main {
    public static void main(String[] args) {
        Stack s = new Stack();
        s.add(1);
        s.add(2);
        s.add(3);
        System.out.println(s.peek());
        while(!s.isEmpty()) {
            System.out.println(s.pop());
        }
    }
}
 
cs

 

 

반응형

스프링프레임워크에서 JUnit 을 이용한 API 테스트 : Spring Framework + JUnit + JNDI

junit 없이 매번 was 재기동 및 postman 호출로 api 결과들을 확인하는 방식으로 테스트를 하다 

junit 을 한 번 사용해 볼까 하여 기존 프로젝트에 설정을 잡고 사용해보았다.

 

혹여 본 포스팅 내용을 참고 하신다면, 프로젝트마다 context 및 프로젝트내에서 사용중인 lib version 등의 설정이 다르므로 제가 작성한 글은 그냥 참고만 하시길 바랍니다. 

실제 프로젝트에서 사용중인 코드 내에서 일부를 발췌하였으므로 github 등에 소스 공유는 힘듭니다.

언제가 될진 모르겠지만 별도의 프로젝트를 만들어 추후 github에 올려놓고 repository 링크를 공유하겠습니다..

출하면 안될 것 같은 패키지 경로, 실제 소스, 결과 등의 코드는 생략하였습니다.

 

[pom.xml]

필요한 의존성 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.7</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>3.2.2.RELEASE</version>
    <scope>test</scope>
</dependency>
cs

 

[ApiControllerTest.java]

src/test/java 밑에 패키지 생성 후 아래와 같이 작성한다.

※ 테스트 케이스 생성 방법

테스트를 하고자 하는 클래스 선택 -> new -> JUnit Test Case

실제 소스 경로와 동일한 경로의 테스트 소스 경로가 만들어진다.

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
package ~;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import java.nio.charset.Charset;
import java.util.Map;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
import ~.BaseController;
import ~.IdolApiController;
import ~.IdolApiService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
                  "classpath:bmp/spring/context-*.xml" 
                , "classpath:bmp/sqlmap/mybatis-config.xml" 
                , "classpath:bmp/servlet/servlet-*.xml"
              
                })
@ActiveProfiles("dev")
@TransactionConfiguration(transactionManager="txManager", defaultRollback = false)
@Transactional
@WebAppConfiguration
public class ApiControllerTest3 {
    protected static final Logger logger = LoggerFactory.getLogger(ApiControllerTest3.class);
    
    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mvc;
    
    private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
            MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
    
    @Before
    public void setUp() throws Exception {
        mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
    }
    
    
    @Test
    public void testIdolApiController() throws Exception {
        
        JSONObject jobj = new JSONObject();
        jobj.put("id""test");
        jobj.put("clientIp""172.0.0.1");
        jobj.put("content""testcontent.mpg");
        String p = jobj.toString();
        
        MvcResult rs = null;
        
        rs = mvc.perform(post("/getdata")
                                       //.content("{\"said\":\"test\", \"clientIp\":\"172.0.0.1\", \"content\":\"testcontent.mpg\"}")
                                         .content(jobj.toString())
                                         .contentType(contentType)
                        )
        .andDo(print())
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andReturn();
        
        logger.debug("---------------------------");
        logger.debug("result : {}", rs.getModelAndView().getModel());
        
    }
}
 
cs

31 L : junit test 를 위해 클래스에 붙여주는 annotation

32 L : 참조할 bean 들의 설정이 잡혀있는 context 경로 (classpath/file 및 절대/상대 경로 주의)

38 L : 활성시킬 profile 지정 (profile과 관련된 내용은 여기를 참고)

39 L : 트랜잭션 설정

40 L : 트랜잭션 적용

41 L : web controller 테스트를 위한 annotation

53 L : @Before를 메소드에 달아줄 경우 @Test 실행 전에 실행된다.

55 L : MockMvc 빌드를 위한 코드 ( 정확히 모르니 추후 Mock 관련 공부를 하며 보충 )

70~77 L : 

post(url) : post 방식 호출

content(String) : body 로 전송

contentType(contentType) : 호출시 content Type 지정

andDo(print()) : 결과 출력

andReturn() : 결과값 리턴(MvcResult rs 변수에 담기)

 

※ context 가 아닌 특정 controller 만 주입 받아 처리하고 싶은 경우 아래와 같이..

참고 : https://thswave.github.io/java/2015/03/02/spring-mvc-test.html

@Mock
VodApiService vodApiService;
@InjectMocks
VodApiController vodApiController;
private MockMvc mvc;

@Before
public void setUp() throws Exception {
      MockitoAnnotations.initMocks(this);
      mvc = MockMvcBuilders.standaloneSetup(vodApiController).build();
}

[junit 실행 결과]

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
MockHttpServletRequest:
         HTTP Method = POST
        Request URI = /getData
          Parameters = {}
             Headers = {Content-Type=[application/json;charset=UTF-8]}
 
             Handler:
               Type = ~
             Method = ~
 
  Resolved Exception:
                Type = null
 
        ModelAndView:
           View name = jsonView
                View = null
           Attribute = result_msg
               value = 
           Attribute = data
              value = {~}
           Attribute = result_cd
               value = 1
 
            FlashMap:
 
MockHttpServletResponse:
              Status = 200
       Error message = null
             Headers = {Content-Type=[application/json;charset=UTF-8], Pragma=[no-cache], Cache-Control=[no-cache, no-store, max-age=0], Expires=[1]}
        Content type = application/json
               Body = {~}
       Forwarded URL = null
      Redirected URL = null
             Cookies = []
DEBUG: ~ - ---------------------------
DEBUG: ~ - result : {~}
cs

 

여기서 log가 debug 레벨로 잘 출력 됐음을 확인 할 수 있는데, 이는 log4j 설정이 잡혀 있는 파일 (본 프로젝트에선 log4j2.xml)을 src/test/resources 밑에 복사 붙여넣기 한 후, log 레벨을 debug 로 내렸기 때문이다.

src/main/resources 내의 log4j 설정 파일을 읽기 전에 src/test/resources 내의 log4j 설정 파일을 우선적으로 읽는다. 관련 내용은 여기를 참고.

log4j 설정 관련 글은 여기를 참고.

 

JNDI 를 사용하고 있는 경우, jUnit test

보통은 위와 같이 테스트 할 수 있지만,

JNDI 를 사용하여 DB 설정이 잡혀 있는 경우, 별도의 처리가 필요하다.

JNDI 설정의 경우 was 에 DB 설정 정보가 존재하며 was 구동시 이를 읽어 들이는 방식인데(정확히 모름+공부필요),

was가 아닌 JUnit 구동시 DB설정정보를 읽어들이지 못해 이와 관련된 Bean을 주입 받을 때 에러가 발생하기 때문이다.

이를 해결하는 방법은 아래와 같이 두가지 정도가 있는 듯 하다.

 

[방법 1 : 테스트용 context 별도 설정]

1) JNDI 설정이 잡혀있는 context-datasource.xml 을 복사하여 src/test/resources 경로 밑에 붙여넣은 후 파일명을 test-datasource.xml 로 바꿔준다.

[example]

아래와 같은 JNDI 설정을

1
2
3
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName" value="java:/jdbc/testJNDI" />
</bean>
cs

아래와 같이 바꿔준다. (was에 있을 DB접속정보(jndi 설정)를 context에 직접 작성)

1
2
3
4
5
6
7
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/> 
    <property name="url" value="jdbc:mysql://~:3306/~?useSSL=false&amp;serverTimezone=UTC"/> 
    <property name="username" value="~"/> 
    <property name="password" value="~"/>
</bean>
 
cs

2) 위에서 작성한 JUnit테스트 소스인 ApiControllerTest.java 를 아래와 같이 수정한다.

@ContextConfiguration 부분에 선언된 context 에 1~2) 번에서 작성한 context (jndi 설정을 걷어낸 DB설정(test-datasource.xml))를 추가한다.

1
2
3
4
5
6
@ContextConfiguration(locations = {
                  "classpath:bmp/spring/context-*.xml"
              , "classpath:test-*.xml"
                , "classpath:bmp/sqlmap/mybatis-config.xml" 
                , "classpath:bmp/servlet/servlet-*.xml"
                })
cs

 

위에서 언급한 log4j 설정과 마찬가지로, src/test/resources 가 src/main/resources 보다 선행돼서 읽히기 때문에 src/main/resources 밑의 context 에 선언된 Bean 과 동일한 이름의 Bean이 src/test/resources 밑의 context 에 선언돼 있어도 상관이 없는 듯 하다.

 

 

[방법 2 : JNDI 바인딩]

1) SpringJUnit4ClassRunner 를 상속받는 ExtSpringJUnit4ClassRunner 작성.

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
package ~;
 
import org.junit.runners.model.InitializationError;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jndi.JndiTemplate;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
public class ExtSpringJUnit4ClassRunner extends SpringJUnit4ClassRunner {
 
    public ExtSpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
        super(clazz);
        // TODO Auto-generated constructor stub
        try {
            bindJndi();
        } catch (Exception e) {
            
        }
    }
    
    private void bindJndi() throws Exception {
        SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
        builder.activate();
        
        JndiTemplate jt = new JndiTemplate();
        
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://~:3306/~?useSSL=false&amp;serverTimezone=UTC");
        ds.setUsername("~");
        ds.setPassword("~");
        
        jt.bind("java:/jdbc/testJNDI", ds);
    }
}
 
cs

위와 같이 Jndi 바인딩을 java 소스로 처리.

관련 부분은 여기를 참고했음.

(이런식으로 JNDI 설정을 잡아줄 수 있는 걸 보면 다른 부분 설정도 이와 비슷하게 처리가 가능할 듯)

 

2) 위에서 작성한 JUnit테스트 소스인 ApiControllerTest.java 를 아래와 같이 수정한다.

1
@RunWith(ExtSpringJUnit4ClassRunner.class)
cs

SpringJUnit4ClassRunner 대신 위에서 작성한 ExtSpringJUnit4ClassRunner를 사용한다.

나머지 부분은 동일하다.

(jndi 설정을 java소스(ExtSpringJUnit4ClassRunner.java)에서 잡아주었으므로 방법 1과 같이 별도의 context 를 @ContextConfiguration에 추가해줄 필요가 없다)

 

JUnit 사용시 Maven Build 주의사항

JUnit을 사용하여 테스트를 하고 있고 Maven Build 로 war 파일을 생성하고 있다면 주의해야 할 점.

테스트 코드 결과가 실패인 경우, Maven Build 의 결과가 Failure (실패)로 떨어진다.

이땐 아래와 같이 해결한다.

 

[방법 1 : UI 에서 build option 지정]

프로젝트 우클릭 > Run Configurations.. > 좌측 리스트에서 Maven Build 중 해당 프로젝트 선택 > skip Tests 옵션 체크

 

[방법 2 : pom.xml 에 build option 수정]

1
2
3
4
5
6
7
8
9
 <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.18.1</version>
    <configuration>
        <useSystemClassLoader>false</useSystemClassLoader>
        <skipTests>true</skipTests>
    </configuration>
</plugin>
cs

위에서 7번라인과 같이 skipTests 옵션 true 지정.

해당 내용은 여기를 참고했음.

 

 

전체적인 JUnit에 대한 설명 및 설정등은 여기를 참고했음.

(이것저것 공부하고 관련 레퍼런스 찾아보고 블로그/깃헙 돌아다니다 보면 이상하리만큼 자주 도달하게 되는 곳이 있는데 그중 하나인 곳.. 항상 감사드립니다..)

 

본 포스팅은 다른 포스팅들과 마찬가지로 수시로 수정 및 업데이트 하겠습니다.

 

참고 :

https://effectivesquid.tistory.com/entry/Spring-test-%EC%99%80-Junit4%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8

반응형

www.ibatis.com 폐쇄로 dtd 경로 로 지정 불가. 

www.ibatis.com 을 ibatis.apache.org/dtd 로 수정해줘야함.

 

[잘못된(폐쇄된) dtd 경로]

<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd">

[정상 dtd 경로]

<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">

 

참고 :

https://storing.tistory.com/105

 

 

반응형

 

1. 네이밍

의미없는 코드값(숫자로 된 코드값), 클래스명 등 사용 금지.

변수명, 함수명만 봐도 어떤 역할을 하는 변수/함수 인지 알 수 있도록 네이밍하기.

※ 실사례

실제로 기존에 해왔던 프로젝트들을 돌이켜 봤을 때, 아무 의미 없는 숫자들로 따져있는 공통코드 및 클래스/함수 명이 상당히 많이 존재했다. 이런 코드들은 외워지지도 않고 프로젝트 내내 개발자들이 명세서를 찾아봐야 하는 엄청난 수고를 하게 만든다. 또한 명세서를 필요 이상으로 철저히 관리 해야하는 스트레스를 받게 한다.

분류가 애매하거나 코드값이 너무 많아 숫자로 분류하는 것은 어쩔 수 없겠으나, 그렇지 않은 경우엔 최대한 삼가해야 한다고 본다.

실제로 다음은 TV광고까지 진행됐던, 국내 최대 통신사 중 하나의, 동접자 2천명 이상의 아이돌 방송 관련 프로젝트를 수행할 때 사용한 공통코드이다. (코드값을 정의한 PM님을 설득하지 못해 어쩔 수 없이 사용했다...)

LV00 : 카메라촬영

LV11 : 앱촬영

MT00 : 멀티뷰카메라촬영

MT11 : 멀티뷰앱촬영

VR00 : VR카메라촬영

VR36 : VR앱촬영

아무 의미 없는 숫자들이 사용됐으며 규칙성 또한 존재한다고 보기 힘들다.

그렇다보니 프로젝트 내내 코드값 의미하는 바가 헷갈려 매번 명세서를 찾아보아야 했다.

위와 같은 코드 값은 아래와 같이 수정할 수 있다.

LVCAM : 카메라촬영

LVAPP : 앱촬영

MTCAM : 멀티뷰카메라촬영

MTAPP : 멀티뷰앱촬영

VRCAM : VR카메라촬영

VRAPP : VR앱촬영

위와 같이 네이밍은 작업능률을 올리고 유지보수성을 높이기 위해 가장 중요하고 따르기 쉬운 규칙이지만,

실제론 가장 지켜지지 않고 있는 악습이라고 생각한다.

 

2. 함수

인자값은 적을수록 좋다(단항함수가 최고)

인자값이 너무 많을 경우 객체를 인자값으로 받도록 설계한다

함수는 하나의 기능만 수행하도록 해라

위에서 아래로 읽히도록 해라

 

boolean 값을 인자값으로 받느니 함수를 별도로 만들어라

※ 실사례

//boolean 을 인자값으로 취하는 메소드 (부적절)
public JsonObject httpUrlConnection(boolean isJson, String targetUrl, String method){}

//boolean 을 인자값으로 받는 대신 아래와 같이 메소드 두개로 분리 (적절)
//isJson 이 false 일 때 사용하는 메소드
httpUrlConnection(String targetUrl, String method){}
//isJson 이 true 일 때 사용하는 메소드
httpUrlConnectionJsonBody(String targetUrl, String method){}
위 함수 2개로 처리

코드결과값보단 예외를 활용하라

※ 실사례

//boolean 을 리턴하는 코드 (부적절)
public boolean ftpFileUpload(...){
   return ftpClient.storeFile(remoteFilePath, inputStream);
}
//예외를 던지는 코드 (적절)
public void ftpFileUpload(...) throws Exception{
   try{
      ftpClient.storeFile(remoteFilePath, inputStream);
   } catch(Exception e) {
      logger.error(e.getMessage);
      throw e;
   }
}

 

3. 주석

주석을 많이 달 시간에 소스 리팩토링을 하라

주석까지 현행화되는 경우는 드물다, 주석을 믿지 말라

정규식, 리턴타입 에 대한 설명, TODO 에 대한 주석은 유용하다(상식적으로 필요한 주석만 달라는 의미같다)

 

4. 형식 맞추기

논리적으로 비슷한 함수끼리 모아서 작성.

함수 1에서 함수2를 호출한다면, 함수1 바로 밑에 함수2를 작성하여 신문읽듯 코드를 읽을 수 있게 작성하라. (호출되는 함수를 호출하는 함수에 가까이 작성)

마찬가지로 변수선언은 사용하는 위치와 최대한 가까이 둔다. (예외로 멤버변수는 최상단에 놓는게 관습)

적절한 행구분은 필수(새로운 개념/논리는 행구분을 하여 끊어주자).

팀 내 형식 규칙을 따를 것

 

5. 객체와 자료구조

객체는 자료(멤버변수)를 숨기고(private) 함수는 공개(public)한다. (캡슐화)

자료구조는 자료는 공개하고 함수는 숨긴다.

 

6. 오류 처리

오류코드 정의 및 리턴보단 try catch 문을 사용하라

null을 주고받지 말라 (nullpointerexception 방지 및 불필요한 null 체크 생략이 가능)

 

7. 경계

학습테스트(외부라이브러리(ex: log4j, aspectj 등)의 충분한 연습과 학습)의 중요성

Adapter 패턴(캡슐화)을 사용하여 외부라이브러리가 직접적으로 사용되지 않도록 하자

 

8. 단위테스트

1) 실패 케이스 부터 작성

2) 실패를 통과하는 실제 코드 작성

테스트는 한번에 1개의 개념만 테스트

assert 문도 가급적 한개만 사용하도록 테스트코드 작성

 

9. 클래스

1) SRP : single responsibility principal/단일 책임원칙 

클래스는 한 가지 기능(역할)만 해야 한다.

2) 응집도(Cohesion)가 커야 좋다

인스턴스 변수를 여러 메소드에서 사용해야 응집도가 높다 할 수 있다.

인스턴스 변수가 많다면? 클래스가 분리되어야 한다는 의미(클래스는 작을 수록 좋다)

3) OCP : open closed principle

확장에 개방적이고 수정에 폐쇄적이어야 한다

 

10. 동시성(멀티스레드)

[방어전략]

1) 동시성코드는 타 코드와 분리 (SRP)

2) 임계영역을 최소화(동기화 하는 부분을 최대한 작게)

3) 사본 사용(객체 복사)

4) 테스트

4-1) 단일 스레드에서 먼저 확인 후 멀티 스레드 작성

4-2) 보조코드(wait(), sleep(), yield() 등) 사용하여 테스트/AOF(aspect oriented framework), cglib 등과 같은 멀티스레드 테스트 도구 사용

4-3) 다양한 os에서 테스트

4-4) 프로세서 수보다 많은 스레드 테스트 (스와핑시 문제발생하는 경우가 존재)

* 스와핑 : 프로세스의 cpu 할당 시간이 끝나면, 메모리를 보조 기억장치(하드디스크)로 내보내고 다른 프로세스 메모리 불러 들이는 것

 

 

2019.12.28

 

반응형

'etc. > books' 카테고리의 다른 글

[Book] HTTP 완벽 가이드  (0) 2020.03.17
[Book] 객체지향의 사실과 오해  (0) 2020.02.23
[Book] 소프트웨어 장인  (0) 2020.01.16
[Book] 프로그래머의 길, 멘토에게 묻다  (0) 2019.11.22
[Book] Head First : Design Patterns  (2) 2019.06.04

[Generic (제네릭)]

클래스 내부에서 사용할 데이터 타입을 인스턴스 생성시 확정하는 것을 제네릭이라 한다.

제네릭은 다양한 타입의 객체를 다루는 메소드 및 컬렉션 클래스를 컴파일 시, 타입 체크를 해주는 기능을 한다.

객체 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄인다.

 

※ ArrayList 같은 컬렉션 클래스는 다양한 종류의 객체를 담을 수 있지만, 보통 아래와 같이 한 종류의 객체를 담는 경우가 더 많다. 또한 꺼낼 때 마다 타입체크를 하고 형변환 하는 것은 아무래도 불편할 수 밖에 없는데, 제네릭이 이와 같은 불편함 들을 해소해준다.

List<Person> list = new ArrayList<>();
list.add(new Person("영심이"));
list.add(new Person("홍길동"));
list.get(0);

 

1. 제네릭 타입

타입을 파라미터(<T>)로 가지는 클래스 혹은 인터페이스

타입 파라미터(<T>) 를 클래스 혹은 인터페이스 명 뒤에 두어 선언

public class Sample<T>{}
public interface Sample<T>{}

[Print.class (제네릭 타입)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package Java.Generic;
 
public class Print<T> {
    
    private T text;
    
    public void setText(T text) {
        this.text = text;
    }
    
    public void printText() {
        System.out.println(text);
    }
}
 
cs

 

[Main.class]

p.text 는 String,

b.text 는 Integer(int) 데이터 타입을 갖게된다.

: 제네릭을 사용하여 클래스를 정의할 때 데이터 타입을 정하지 않고, 인스턴스를 생성할 때 타입을 저장

 

※ jdk 1.7 버전 이상은 실제 타입 생략 가능

Print<Integer> p = new Print<>();  // Print<Integer> p = new Print<Integer>(); 와 같음

※ Raw Type : 타입 매개변수가 없는 제네릭 클래스

generic은 jdk 1.5 버전에 지원됐으며, 1.5 버전 이전의 소스와 문제가 생기는걸 방지하기 위해 Raw Type 을 허용

아래와 같이 데이터타입을 주지 않고 선언 할 경우 데이터 타입(타입 매개변수)은 Object로 간주한다.

List list = new ArrayList<>();   //List<Object> list = new ArrayList<>(); 와 같음

[제네릭의 장점]

1. 타입안정성 제공 :

명시한 타입의 데이터가 아닌 다른 형태의 데이터 타입으로의 형변환을 방지 (위 Main.class 의 8번, 14번 참고)

2. 타입체크와 형변환 생략 가능 :

다룰 객체의 데이터 타입을 미리 명시

 

[다중 타입매개변수]

타입매개변수가 N개인 경우, 아래와 같이 사용 가능

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package Java.Generic;
 
public class Pair<K, V> {
 
    private K key;
    private V value;
    
    public Pair(K key, V value){
        this.key = key;
        this.value = value;
    }
    
    public K getKey() {
        return key;
    }
    
    public V getValue() {
        return value;
    }    
}
 
cs

Client(호출부)

Pair<String, Integer> pair1 = new Pair<>("key1", 1);
System.out.println(pair1.getKey());    //key1
System.out.println(pair1.getValue());  //1

Pair<Integer, Integer> pair2 = new Pair<>(1, 2);
System.out.println(pair2.getKey());     //1
System.out.println(pair2.getValue());   //2

[제네릭과 다형성]

public class Print{}
public class ChilePrint extends Print{}

위처럼 Print 클래스, 이를 상속하는 ChildPrint 클래스가 있을 때

아래와 같이 사용 가능.

1
2
3
4
5
6
7
8
9
10
public class Client {
    public static void main(String[] args) {
        List<Print> list = new ArrayList<>();
        list.add(new Print());
        list.add(new ChildPrint());
        
        Print p = list.get(0);
        ChildPrint cp = (ChildPrint)list.get(0);    
    }
}
cs

Print 를 상속하는 ChildPrint 는 Print 타입파라미터를 갖는 list 인스턴스에 add 할 수 있다.

 

 

2. 제네릭 메소드

리턴 타입 및 매개변수 타입을 타입 매개변수로 갖는 메소드

일반 클래스의 메소드에서도 타입 매개변수를 사용하여 제네릭 메소드 정의 가능

리턴타입 앞에 타입 매개변수(<>)를 추가한 후, 리턴타입과 매개타입을 타입 매개변수로 사용가능

 

[Cart.class]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package Java.Generic;
 
import java.util.ArrayList;
import java.util.List;
 
public class Cart<T> {
    
    private List<T> items;
    
    public Cart() {
        if(items == null)items = new ArrayList<>();
    }
    
    public void setItem(T item) {
        this.items.add(item);
    }
    
    public List<T> getItems() {
        return items;
    }
    
}
 
cs

[Calculator.class (제네릭 메소드)]

1
2
3
4
5
6
7
8
9
10
11
12
package Java.Generic;
 
public class Calculator {
    public static <T> int add(Cart<T> cart1, Cart<T> cart2) {
        int result = 0;
        
        result = cart1.getItems().size() + cart2.getItems().size();
        
        return result;
    }
}
 
cs

[Client.class (호출부)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package Java.Generic;
 
public class Client {
    public static void main(String[] args) {
        
        Cart<String> cart1 = new Cart<>();
        cart1.setItem("초콜렛");
        cart1.setItem("슬리퍼");
        
        Cart<String> cart2 = new Cart<>();
        cart2.setItem("생수");
        
        int totalSize = Calculator.add(cart1, cart2);
        System.out.println(totalSize);  // 3
        
    }
}
cs

 

 

3. 타입 파라미터의 제한

상속 및 구현 관계를 이용하여 타입 제한

※ 상위 타입이 interface인 경우에도 extends 키워드 사용.

public <T extends 상위타입> 리턴타입 메소드(매개변수, ...) {...}

[Product.class]

최상위 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package Java.Generic;
 
public class Product {
    
    private String product;
    
    public void setProduct(String product) {
        this.product = product;
    }
    
    public String getProduct() {
        return this.product;
    }
}
 
cs

[Food.class]

Product를 상속받는 하위 클래스

1
2
3
4
package Java.Generic;
 
public class Food extends Product{}
 
cs

[Fruit.class]

Food 를 상속받는 하위 클래스

1
2
3
4
package Java.Generic;
 
public class Fruit extends Food{}
 
cs

[Cart.class]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package Java.Generic;
 
import java.util.ArrayList;
import java.util.List;
 
public class Cart<T> {
    
    private List<T> items;
    
    public Cart() {
        if(items == null)items = new ArrayList<>();
    }
    
    public void setItem(T item) {
        this.items.add(item);
    }
    
    public List<T> getItems() {
        return items;
    }
    
}
 
cs

[Calculator.class (제네릭 메소드)]

타입 파라미터를 Food 로 제한한다. (Food 혹은 Foor 하위 클래스만 타입 파라미터로 받을 수 있다)

1
2
3
4
5
6
7
8
9
10
11
12
package Java.Generic;
 
public class Calculator {
    public static <extends Food> int add(Cart<T> cart1, Cart<T> cart2) {
        int result = 0;
        
        result = cart1.getItems().size() + cart2.getItems().size();
        
        return result;
    }
}
 
cs

[Client.class (호출부)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package Java.Generic;
 
public class Client {
    public static void main(String[] args) {
        
        Cart<Food> cart1 = new Cart<>();
        Product p = new Food();
        p.setProduct("초콜릿");
        cart1.setItem((Food)p);
        
        Cart<Food> cart2 = new Cart<>();
        Product p2 = new Food();
        p2.setProduct("과자");
       cart2.setItem((Food)p2);
        
        int totalSize = Calculator.add(cart1, cart2);
        System.out.println(totalSize);
    }
}
cs

※ 해당 메소드 호출 시, 타입 파라미터를 섞어서 사용 할 수 없다.

: 16번 라인의 .add(...) 호출 시, 넘겨주는 매개변수 cart1 의 타입 파라미터(6번 라인의 Cart<Food>)에 의해 .add(...) 메소드의 타입파라미터(Calculator.class의 4번 라인의 <T>)가 정해진다.

 

예를 들어, 아래와 같은 경우

14번 라인에서 빨간줄이 그어진다. 

cart3 의 Fruit 으로 .add(...) 메소드의 타입파라미터가 정해지지만, Food 를 담고있는 cart4 를 매개변수로 넣으려고 하고 있으므로 not applicable for the arguments 에러가 발생한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Java.Generic;
 
public class Client {
    public static void main(String[] args) {
        
        Cart<Food> cart1 = new Cart<>();
        Calculator.add(cart1, cart1);
        
        Cart<Fruit> cart2 = new Cart<>();
        Calculator.add(cart2, cart2);
                
        Cart<Fruit> cart3 = new Cart<>();
        Cart<Food> cart4 = new Cart<>();
        Calculator.add(cart3, cart4);    //compile error
    }
}
cs

 

 

4. 와일드카드

1. 제네릭 타입<?>: Unbounded Wildcards(제한 없음)

  타입 파라미터로 모든 클래스나 인터페이스 타입이 올 수 있다.

2. 제네릭 타입<? extends 상위 타입>: Upper Bounded Wildcards(상위 클래스 제한)

  타입 파라미터로 상위 타입의 하위 타입만 올 수 있다.

3. 제네릭 타입<? super 하위 타입>: Lower Bounded Wildcards(하위 클래스 제한)

  타입 파라미터로 하위 타입의 상위타입만 올 수 있다.

 

[example]

public class Product {}
public class Food extends Product {}
public class Fruit extends Food {}

[Calculator.class : 타입 파라미터 제한과 와일드카드 사용의 차이]

add1(...) : 타입 파라미터를 제한

add3(...) : 와일드카드를 사용하여 매개변수의 타입을 제한

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
package Java.Generic;
 
public class Calculator {
    public static <extends Food> int add1(Cart<T> cart1, Cart<T> cart2, Cart<T> cart3) {
        //제네릭 메소드에서의 타입 파라미터의 제한
        //타입파라미터를 Food 로 제한
        //매개변수는 타입파라미터인 T를 갖는 Cart이며, T는 Food 및 Food 의 subclass 로 제한
        int result = 0;
        result = cart1.getItems().size() + cart2.getItems().size() + cart3.getItems().size();
        return result;
    }
    
//    public static <T super Food> int add2(Cart<T> cart1, Cart<T> cart2, Cart<T> cart3) {    
//        //제네릭 메소드에서 타입 파라미터를 제한할 때 위와 같은 super키워드는 불가 
//        //compile error
//        int result = 0;
//        result = cart1.getItems().size() + cart2.getItems().size() + cart3.getItems().size();
//        return result;
//    }
    
    public static int add3(Cart<super Food> cart1, Cart<?> cart2, Cart<extends Food> cart3) {
        //매개변수의 타입을 와일드카드 (?)를 사용하여 제한
        //cart1 맥대변수(Cart)의 타입파라미터 : Food 및 Food 를 subclass 
        //cart2 매개변수(Cart)의 타입파라미터 : 아무 타입이나 담고있는 Cart
        //cart3 매개변수(Cart)의 타입파라미터 : Food 및 Food 의 subclass
        int result = 0;
        result = cart1.getItems().size() + cart2.getItems().size() + cart3.getItems().size();
        return result;
    }
}
 
cs

[Client.class]

1. add1사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package Java.Generic;
 
public class Client {
    public static void main(String[] args) {
        
        Cart<Food> cart1 = new Cart<>();
        Calculator.add1(cart1, cart1, cart1);    //타입파라미터를 Food 로 지정
        
        Cart<Fruit> cart2 = new Cart<>();
        Calculator.add1(cart2, cart2, cart2);    //타입파라미터를 Fruit 으로 지정
        
        Cart<Fruit> cart3 = new Cart<>();
        Cart<Food> cart4 = new Cart<>();
        Calculator.add1(cart3, cart3, cart4);    //car3 매개변수에 의해 Fruit 으로 타입파라미터가 정해지지만, cart4 Food를 넣으려하기 때문에 Compile Error
        
    }
}
 
cs

2. add3 사용

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.Generic;
 
public class Client {
    public static void main(String[] args) {
        
        Cart<Product> cart1 = new Cart<>();
        Product p = new Product();
        p.setProduct("바가지");
        cart1.setItem(p);
        
        Cart<String> cart2 = new Cart<>();
        cart2.setItem("연필");
        
        Cart<Fruit> cart3 = new Cart<>();
        Product p2 = new Fruit();
        p2.setProduct("사과");
        Product p3 = new Fruit();
        p3.setProduct("수박");
        cart3.setItem((Fruit)p2);
        cart3.setItem((Fruit)p3);
        
        int totalSize = Calculator.add3(cart1, cart2, cart3);
        System.out.println(totalSize);
    }
}
cs

 

※ 

E - Element (used extensively by the Java Collections Framework)

K - Key

N - Number

T - Type

V - Value

 

 

참고:

https://devbox.tistory.com/entry/Java-%EC%A0%9C%EB%84%A4%EB%A6%AD

https://movefast.tistory.com/74

https://ict-nroo.tistory.com/42

 

 

 

 

반응형

'back > java' 카테고리의 다른 글

[Java] lambda 람다 2 (메소드참조)  (0) 2020.01.17
[Java] lambda 람다 1 (기본문법)  (0) 2020.01.15
[Java] Compile  (0) 2019.12.22
[Java] Collection Framework  (0) 2019.12.21
Comparator, Comparable + Arrays.sort() 그리고 인터페이스..  (0) 2019.09.02

 

1. 테스트를 위한 계산기 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package Calculator;
 
public class Calculator {
    
    int add(int i, int j) {
        return i+j;
    }
    
    int subtract(int i, int j) {
        return i-j;
    }
    
    int muliply(int i, int j) {
        return i*j;
    }
    
    int devide(int i, int j) {
        return i/j;
    }
}
 
cs

 

2. junit 테스트 케이스 생성

1) 클래스 생성 버튼 우측의 화살표 버튼을 눌러 JUnit Test Case 선택

 

2) JUnit Test 를 위한 JUnit 클래스 생성

Test 대상 Class뒤에 Test 를 붙여 이름을 짓는게 관례(Calculator+Test)

 

3. junit 테스트 케이스 작성

1) 기본적으로 생성된 test() 메소드 제거 후 아래와 같이 코딩

: add() 메소드 테스트

: assertEquals 는 버전에 따라 deprecated 된 메소드가 있으니 잘 보고 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package Calculator;
 
import static org.junit.Assert.*;
 
import org.junit.Test;
 
public class CalculatorTest {
 
    @Test
    public void add() {
        Calculator cal = new Calculator();
        cal.add(12);
        assertEquals(""33);
    }
 
}
 
cs

 

2) 지역 변수 cal 을 필드로 빼서 사용하기

11번라인의 Calculator cal = new Calculator(); 를 8번 라인으로 빼서 필드로 사용이 가능하나, @Before를 사용하자.

필드 초기화를 전역에서 명시적으로 수행할 경우, 테스트간 독립성이 보장되지 않는다.

반면 @Before 사용시 add(), devide() 와 같은 테스트 메소드 실행시마다 인스턴스를 초기화 하여 

테스트간 독립성이 보장된다.

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
package Calculator;
 
import static org.junit.Assert.*;
 
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
 
public class CalculatorTest {
 
    private Calculator cal;
    
    @Before
    public void setup() {
        cal = new Calculator();
        System.out.println("setup !!!");
    }
    
    @Test
    public void add() {
        int r = cal.add(23);
        assertEquals("sc"5, r);
        System.out.println("add !!!");
    }
 
    @Test
    public void devide() {
        int r = cal.devide(33);
        assertEquals("success"1, r);
        System.out.println("devide !!!");
    }
 
    @After
    public void teardown() {
        System.out.println("teardown !!!");
    }
    
}
 
cs

 

[실행 결과]

setup !!!
devide !!!
teardown !!!
setup !!!
add !!!
teardown !!!

위처럼 @Before, @After 는 테스트 메소드 실행시마다 호출됨을 확인 할 수 있다.

 

 

참고 : 박재성님의 유투브 강의 https://www.youtube.com/watch?v=tyZMdwT3rIY

반응형

1. Telescoping Pattern (텔레스코핑 패턴)

생성자와 오버로딩을 사용하는 패턴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package design_pattern.builder;
public class CoordinateTelescoping {
    //텔레스코핑 생성자 패턴
    //Telescoping constructor
 
    private final int x;
    private final int y;
    private int w;
    private int h;
    
    public CoordinateTelescoping(int x, int y) {
        this(x, y, 00);
    }
    public CoordinateTelescoping(int x, int y, int w, int h) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }
    
}
 
cs

매개변수 증가시 코드량이 많아지고 무거워지는 문제,

초기화가 필요 없는 값도 무조건적으로 넘겨야 하는 경우가 존재

 

2. JavaBean Pattern (자바빈 패턴)

getter, setter 사용하는 패턴

(일반적으로 많이 사용되는 Value Object)

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 design_pattern.builder;
public class CoordinateJavaBean {
    //자바 빈 패턴
    //java bean pattern
    
    private int x=0;
    private int y=0;
    private int w=0;
    private int h=0;
    
    public void setX(int x) {
        this.x = x;
    }
    public void setY(int y) {
        this.y = y;
    }
    public void setW(int w) {
        this.w = w;
    }
    public void setH(int h) {
        this.h = h;
    }
    
}
 
cs

객체가 완전하게 생성되었는지 보장 불가(동결(freezing) 보장 불가)

CoordinateJavaBean b = CoordinateJavaBean();
b.setX(10);

위 경우 x 값만 set한 채 객체를 사용할 수 있으므로 객체가 완전하게 생성되지 않은 상태

 

3. Builder Pattern (빌더 패턴)

텔레스코핑패턴 + 자바빈 패턴의 형태

초기화가 반드시 필요한 멤버변수는 생성자로,

선택적으로 초기화를 해도 되는 필드는 setter 메소드로 작성

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
package design_pattern.builder;
 
public class CoordinateBuilder {
    //빌더 패턴
    //Builder pattern : 텔레스코핑 패턴 + 자바빈 패턴
 
    private final int x;
    private final int y;
    private final int w;
    private final int h;
    
    public static class Builder {
        private final int x;
        private final int y;
        private int w;
        private int h;
        
        public Builder(int x, int y) {
            this.x = x;
            this.y = y;
        }
        
        public Builder setW(int w) {
            this.w = w;
            return this;
        }
        
        public Builder setH(int h) {
            this.h = h;
            return this;
        }
        
        public CoordinateBuilder build() {
            return new CoordinateBuilder(this);
        }
    }
    
    public CoordinateBuilder(Builder builder) {
        x = builder.x;
        y = builder.y;
        w = builder.w;
        h = builder.h;
    }
    
}
 
cs

[객체 생성]

CoordinateBuilder c = new CoordinateBuilder.Builder(10, 10).setW(5).setH(5).build();

생성자로 필수 맴버변수를 초기화 하는 객체 생성, 선택적으로 초기화가 필요한 멤버변수는 setter 사용,
마지막으로 build 를 호출하여 객체 리턴

 

 

참고 :

https://using.tistory.com/71

 

 

반응형

[Set]

중복을 허용하지 않는 자료구조로, 모든 데이터가 Unique 하다.

Set Interface 를 구현하는 HashSet, LinkedHashSet, TreeSet 은 위와 같은 성질을 따른다.

 

[HashSet]

내부적으로 HashMap 을 사용

순서 보장 X

null 입력은 가능하나 null도 한 번만 저장 가능하며 중복될 수 없다

 

[LinkedHashSet]

내부적으로 LinkedHashMap을 사용

순서 보장 O (입력한 순서)

null 입력은 가능하나 null도 한 번만 저장 가능하며 중복될 수 없다

 

[TreeSet]

내부적으로 TreeMap을 사용

인자값으로 받은 Comparator 를 기준으로 정렬, 인자값이 없는 경우 오름차순 정렬

null 입력 불가

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
Set<Integer> set = new HashSet<>();
set.add(null);
set.add(null); //중복허용X
set.add(3);
set.add(2);
set.add(1);
set.add(1);    //중복허용X
 
System.out.print("HashSet: ");
 
Iterator<Integer> si = set.iterator();
while(si.hasNext()) {
    System.out.print(si.next()+" ");
}
System.out.println();
 
 
Set<Integer> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add(3);
linkedHashSet.add(2);
linkedHashSet.add(1);
linkedHashSet.add(1); //중복허용 X
 
System.out.print("linkedHashSet: ");
for(int i : linkedHashSet) {
    System.out.print(i+" ");
}
System.out.println();
 
 
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(3);
treeSet.add(2);
treeSet.add(1);
treeSet.add(1); //중복허용X
 
System.out.print("TreeSet: ");
for(int i : treeSet) {
    System.out.print(i+" ");
}
System.out.println();
 
 
Set<Integer> treeSetDescending = new TreeSet<>(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        //내림차순
        return o2-o1;
    }
});
 
System.out.print("TreeSetDescending: ");
try {
    treeSetDescending.add(null);    //null 허용 X
}catch(NullPointerException ne) {
    System.out.println("exception:"+ne.getMessage());
}
treeSetDescending.add(3);
treeSetDescending.add(2);
treeSetDescending.add(1);
treeSetDescending.add(1); //중복허용X
 
for(int i : treeSetDescending) {
    System.out.print(i+" ");
}
cs

 

[실행 결과]

HashSet: 1 2 3
linkedHashSet: 3 2 1
TreeSet: 1 2 3
TreeSetDescending: 3 2 1

 

[시간복잡도]

HashSet 이 LinkedHashSet, TreeSet보다 성능이 좋으며 메모리를 적게 사용.

   HashSet LinkedHashSet TreeSet
performance Θ(1) Θ(1) Θ(log(n))

 

참고 :

https://javaconceptoftheday.com/hashset-vs-linkedhashset-vs-treeset-in-java/

 

반응형

+ Recent posts