https://github.com/koobdev/Algorithm/blob/master/src/com/company/Chapter5.java

 

GitHub - koobdev/Algorithm

Contribute to koobdev/Algorithm development by creating an account on GitHub.

github.com

 

 

4 나누기 10의 결과를 2.5로 나타내고 싶을 때? (4344)

int ten = 10; 
int four = 4; 

double answer1 = (ten * 1.0) / four; 
double answer2 = (double) ten / four;

 

모든 변수를 double로 설정하면 그만큼 데이터 양도 커지기 때문에 1.0을 곱해주는 방법이나 강제 캐스팅을 이용하자!!

https://github.com/koobdev/Algorithm/blob/master/src/com/company/Chapter4.java

 

백준 단계별로 풀어보기 - while문

https://github.com/koobdev/Algorithm/blob/master/src/com/company/Chapter3.java

 

GitHub - koobdev/Algorithm

Contribute to koobdev/Algorithm development by creating an account on GitHub.

github.com

 

 

 

BufferReader 
 -> 문자 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이다.
 -> 객체 생성시 생성자에 InputStreamReader(System.in)
 -> BufferReader.readLine()하면 string 한줄 전체를 읽어올 수 있다.
 -> BufferReader.readLine()을 사용하려면 예외처리를 꼭 해야한다. (IOException)
 -> 문자를 한 개 씩 읽을 때, StringTokenizer를 활용하자.
       StringTokenizer st = new StringTokenizer(br.readLine(), " ");
       두번 째 매개변수에 구분자를 지정할 수 있다.
       st.nextToken()으로 한개의 토큰 단위로 읽어올 수 있다.
 -> 마지막에 close() 메소드로 닫아준다.


BufferWriter
 -> 문자 출력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림.
     객체 생성시 생성자에 outputStreamReader(System.out)
 -> writer() 메소드를 이용해서 버퍼에 데이터를 쓴다.
 -> flush() 메소드를 이용해서 버퍼에 잔류한 데이터를 모두 내보낸다.
 -> 마지막에 close() 메소드로 닫아준다.

https://github.com/koobdev/Algorithm/blob/master/src/com/company/Chapter2.java

 

GitHub - koobdev/Algorithm

Contribute to koobdev/Algorithm development by creating an account on GitHub.

github.com

 

백준 단계별로 풀어보기 if문!!

https://github.com/koobdev/Algorithm/blob/master/src/com/company/Chapter1.java

 

GitHub - koobdev/Algorithm

Contribute to koobdev/Algorithm development by creating an account on GitHub.

github.com

 

단계별로 풀어보기 입출력과 사칙연산 부분!!

Spring STOMP를 이용하여 실시간 채팅이 가능한 페이지를 만드는중...

 

 

 

요놈이 나를 괴롭혔다...

SockJS로만 하면 연결이 되는데 Stomp를 얹어서 연결을 시도하는 순간 에러가 났다.

spring명세서랑 스택오버플로우랑 깃헙 예제 싹다 뒤져도 저 오류에 대한 해결법을 찾을 수 없었다..

 

 

 

 

<해결을 위한 여러가지 생각의 과정>

 

1. copyToString메소드의 매개변수가 배열로서(Ljava/io/ByteArrayOutputStream;Ljava/nio/charset/CharSet; -> Ljava가 배열이라는것도 이번에 알았다..) 받아져서 그런가?

 

2. json 형이 아니라 string으로 메세지를 넘기려고해서 파싱을 못하는건가?

 

3. 개발도구 Network에서 websocket요청값의 header가 upgrade로 정상 요청됨은 확인을 하였는데 Error메세지에 보니까 content-length : "0"이고 body도 비어 있으니까 헤더에 추가 정보가 필요한가?

 

 

 

 

1번은 Spring.io 문서에서 org.springframework.util.StreamUtils를 찾아보다가 발생한 의문이었는데, 애초에 ByteArray니까 어떤 배열형태라서 문제가 있는것 같지는 않았다. 다만 에러메세지가 이 문제를 계속 가리키고 있었기 때문에 가장 중요문제가 이곳인것 같았다...

 

2번은 아닌것 같았다. 여러 예제를 찾아보고 구글링해보아도, json형으로 id와 msg를 넘기는것이 당연히 더 좋은 형태이겠지만 테스트로 단순하게 메세지를 string형으로 넘기는 예제도 많이 있었다.

 

3번은... content-length에 대해서 찾아보다가

 

 

https://stomp.github.io/stomp-specification-1.2.html#Header_content-length

 

 

 

https://stomp.github.io 에서 우연히 찾게되어서 읽어보았는데, utf-8로 인코딩이 권장된다는 내용이다.

그런데 인코딩이 utf8으로 되어 있지 않았다!!! 약간의 희망을 품고 수정해보았지만

아쉽게도 해결되지 않았다..

 

 

 

 

 

자포자기의 심정으로 처음부터 다시 해보자는 생각을 가지고 메이븐 설정부터 다시 보았는데...

 

 

spring-messaging버전이 5.2.7이다. springframework 버전은 5.2.5인데.. 설마

 

 

 

 

 

.............. 그래도 이틀간의 삽질이 끝나서 좋았다..

 

 

오늘의 결론

버전관리를 잘하자..

 

 

 

 

버전을 따로 빼서 관리하자.. 윤재성 강사님(인프런 강좌)이 괜히 저렇게 하라고 한게 아니었다....ㅠ

pure한(SockJS를 사용하지 않은 W3C표준 WebSocket) Spring WebSocket을 이용해서 양방통신이 가능한 채팅기능을 구현해보려고 하였으나... 어떤 이유인지 ws프로토콜 연결이 안된다... 추후에 다시 해보고.. 안되면 나중에 Java Socket 공부하면서 다시 해보도록 하겠다

 

SockJS를 사용하면 연결이 된다. (?) 

SockJS를 사용하면 http프로토콜을 이용하니까 되는것 같기도.. spring ws 버전 문제인가.. 

SockJS를 사용해서 브라우저와 서버를 연결하여 채팅기능으로서 활용이 가능한지 테스트를 해보겠다.

이번 예제는 테스트로 진행하고, 다음에 STOMP에서는 프로젝트에 적용할수 있도록 개발할 것이다.

 

 

 

dependency 추가

 

위와 같이 의존성을 추가하였다. SockJS도 맨 아래 추가~

(? 지금보니까 SockJS가 webjar이다.... 어쩐지 잘 안되어서 그냥 CDN을 추가했는데 .. ㅎㅎ;;)

 

 

Spring WebSocket을 이용하기 위해선 Configuration파일과 Handler파일이 필요하다 한개씩 알아보자.

 

WebSocketHandler.java

 

Socket의 Connect와 Disconnect, Message처리를 해주는 핸들러이다. TextWebSocketHandler 메소드를 상속받는다. 내가 사용할것이 단순한 채팅에 필요한 메세지형태이기 때문에 TextWebSocketHandler를 상속받았지만, 이미지와 같은 다른 리소스를 통신으로 받는다고 한다면 BinaryWebSocketHandler를 상속받으면 되겠다.

 

afterConnectionEstablished 는 소켓연결이 완료되었을 때 소환되는 메소드이다! 연결 잘 되었는지 session을 찍어봤다.

afterConnectionClosed 는 소켓연결이 끊겼을 때 소환되는 메소드이다! 이 역시 그냥 찍어봤다.

handleTextMessage 는 TextMessage형태로 메세지를 받아온다. 위에서 해준처리는 여러개의 세션이 연결되었을 때, (브라우저가 여러개 연결되었을때.. 즉 채팅방에 user가 여러명일 때) WebSocketSession단위로 List에 담아놓고, 그 리스트에 담겨있는 session을 for문돌리면서 연결된 모든 세션에게 메세지를 보낸다.

이는 브로드캐스팅 방식이다. 안드로이드와 같은(ios도 마찬가지) 앱에서 공지?와 같은 기능에서 자주 사용된다!!

 

이렇게 하면 현재 채팅방에 있는 모든 사람이 내가 보낸 메세지를 볼 수 있고, 또 그에 대한 답변을 모든 사람이 볼 수 있을 것이다. 다만, 아직 채팅방(RoomNum)이 구현이 안되어있기 때문에 방 별로 채팅이 아니라 연결된 모든 session에게 보내는 상황이다.

 

WebSocketConfig.java

 

웹소켓 설정파일이다. WebSocketConfigurer 인터페이스를 구현한다.

 

registerWebSocketHandlers를 오버라이딩해서 사용해야한다. 이는 사용자의 커스텀 핸들러를 사용하여 endPoint를 지정할 수 있다. setAllsetAllowedOrigins를 astarisk로 지정해주면 모든 브라우저에서 사용가능하다.

withSockJS()를 해줌으로써 SockJS를 사용하여 소켓을 만들것을 설정해주는 것이다.

 

chat.jsp

 

클라이언트가 사용할 채팅하는 페이지이다. 

아래 connect() 함수부터 보자. new SockJS()를 사용하여 아까 핸들러에서 지정해준 endPoint를 사용하여 연결을 해준다.

소켓이 열렸으면 onopen이 실행된다 (로그에 성공메세지 찍기)

 

성공적으로 연결되어 메세지를 보냈다고 치자. 그러면 onmessage에서 메세지를 받을 수 있다. event.data로 찍어봤다.

oncloseonerror. 뭐 그렇다.

 

위에 ready는 뭐냐?

준비상태가 되면 connect함수를 실행시켜서 소켓을 열어준다. 열려있는 상태에서, 클라이언트가 메세지를 입력(input형이며 id가 msg이겠지?) 하여 btnSend 버튼을 누르면? send() 함수를 이용하여 서버로 전송한다!!!

 

그러면 서버에서는 아까 위에서 본 핸들러의 handleTextMessage에서 TextMessage매개변수로 받아지는 것이다.

 

 

 

페이지를 열면 connection이 open된다. 

이때 Response Header를 보면 Connection이 upgrade로 되어 있다. 웹소켓 요청이다.

 

Response Headers

 

 

 

내가 보낸 메세지가 서버를 거쳐서~ 다시 ReceiveMessage로 잘 출력이 되는 모습이다.

 

 

 

브라우저를 한개 더 열어서 확인해보니,

서로 다른 세션이 서로의 메세지가 모두 출력되게 보면서 실시간 채팅이 가능한 모습이다.

 

 

 

 

 

이제, STOMP를 이용해서 조금은 더 가벼운 소켓채팅 기능을 프로젝트에 넣어볼 예정이다.

 

<생각해야 할것들>

1. 채팅방 구현 

2. STOMP의 Topic방식과 Queue방식

3. User개인별 소켓 DisConnect

 

+ webjar로 dependency 받는법. springio참고

'JAVA > Spring' 카테고리의 다른 글

컴포넌트 스캔 (@ComponentScan)  (0) 2021.12.17
싱글톤 패턴과 스프링 컨테이너  (0) 2021.12.15
Spring Socket에 대해 알아보자! - 1. WebSocket  (0) 2020.06.18
Spring WebSocket에 대한 내용 정리  (0) 2020.06.11
MyBatis  (0) 2020.04.21

p176. 5장 객체 지향 설계 5원칙 - SOLID

 

 

객체지향 설계에서 중요한 개념인 객체지향의 5원칙에 대해서 알아보자. 책을 보면서 이해한 내용을 위주로 정리한 것이며, 그림으로 이해한 것은 책의 그림을 옮겨와서 정리하도록 하겠다.

 

 

  • SRP (Single Responsibility Principle) : 단일 책임 원칙
  • OCP (Open Closed Principle) : 개방 폐쇄 원칙
  • LSP (Liskov Subtitution Principle) : 리스코프 치환 원칙
  • ISP (Interface Segregation Principle) : 인터페이스 분리 원칙
  • DIP (Dependency Inversion Principle) : 의존 역전 원칙

 

 

 

1. SRP : 단일 책임 원칙

어떤 클래스를 변경해야 하는 이유는 오직 하나 뿐이어야 한다.

역할과 책임이 넘쳐나는 남자클래스....

위의 그림과 같은 남자 클래스가 있다고 가정해보겠다. 남자클래스는 역할과 책임이 너무 많다. 여자친구 클래스에 대해서는 남자친구역할만 해야하는데 어머니 클래스에 대한 아들역할도 동시에 해야하는 상황인 것이다. 

남자클래스의 메소드가(해야할 일) 너무 넘쳐난다... 이와 같은 설계는 단일 책임 원칙에 위반된 설계라고 할 수 있겠다.

 

남자 클래스 분리

남자 클래스의 역할과 책임을 분리해주기 위해서 역할별로 여러 클래스로 나누었다. 이제 다른 클래스에 대한 역할은 신경쓰지 않게 됨으로써 남자 클래스는 부담을 최소화 하였다. 이와 같은 설계원칙이 SRP이다.

 

이번엔 코드로 다른 예를 살펴보자.

 

class 사람{
	String 군번;
	...
}

사람 철수 = new 사람();
사람 영희 = new 영희();


영희.군번 = "1573007545";

 

남자는 무조껀 군대를 가야하고, 여자는 절대로 군대를 갈 수 없는 세상이라고 가정을 해보자. (ㅠ.ㅠ)

사람 클래스에 군번이라는 프로퍼티가 있으면 여자인 영희의 군번을 삽입하는 위의 코드는 이상하다.

 

class 남자{
	String 군번;
    ...
}

class 여자{
	...
}


남자 철수 = new 남자();
여자 영희 = new 여자();


남자.철수 = "1573007545";

 

사람 클래스를 남자클래스와 여자클래스로 나누었고, 사람클래스가 지어야할 역할과 책임을 두개의 클래스로 나눔으로써 줄여주었다. 남자클래스와 여자클래스에 공통으로 들어가는 프로퍼티가 있으면 사람 클래스를 상위클래스로 만들어서 살려놓으면 되고 그게 아니면 없애면 되겠다.

 

 

 

 

 

2. OCP : 개방 폐쇄의 원칙

소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려있어야 하지만, 변경에 대해서는 닫혀 있어야 한다.

OCP를 적용한 예

 

 

운전자 클래스가 있다고 가정해보자. 운전자 클래스 마티즈라는 클래스를 이용하고 있었다. 마티즈 클래스에는 수동창문개방() 과 수동기어조작() 메소드가 존재하였다. 그런데 어느날 운전자 클래스가 쏘나타 클래스로 사용을 변경하였다!!

쏘나타 클래스는 마티즈 클래스와 다르게 자동창문개방() 과 자동기어조작() 메소드를 가지고 있다.

그러면 운전자 클래스는 자동차를 바꾸었으니 알아서 메소드에 적응하여야 할까???

 

운전자 클래스는 자동차라는 상위클래스를 둠으로써 OCP를 적용한 설계를 할 수 있다.

자동차 클래스는 모든 자동차가 공통적으로 가지는 창문개방() 과 기어조작() 메소드를 가지고 있고, 하위 클래스들은 상위 클래스인 자동차 클래스를 상속함으로써 이러한 메소드를 오버라이딩하여 사용할 수 있다.

 

이 때, 자동차 입장에서는 하위 클래스로의 확장에 개방적이고, 운전자 입장에서는 변화에 대해서 폐쇄적이게 된다.

이러한 원칙이 바로 OCP 원칙이다.

 

 

또 다른 예를 상상해보자.

우리는 자바를 사용한다. 자바는 JVM이라는 가상머신이 존재하며 이는 운영체제별로 배포하여 사용자가 어느 운영체제에서 사용하든 .class파일(목적파일)만 있으면 된다.

이 때, 운영체제별 JVM은 확장에 대해서 개방적이고, 개발자의 코드는 변화에 대해서 폐쇄적이다.

 

 

 

 

 

3. LSP : 리스코프 치환 원칙

서브타입은 언제나 자신의 기반타입(base type)으로 교체할 수 있어야 한다.

 

  • 하위 클래스 is a kind of 상위 클래스 : 하위 클래스는 상위클래스의 한 종류이다.
  • 구현 클래스 is able to 인터페이스 : 구현 클래스는 인터페이스할 수 있다.

이는 클래스와 인터페이스를 한 번에 이해할 수 있는 문장이다.

여기서 가장 중요한 점은 하위 클래스는 상위클래스의 한 종류 라는 것이다. 이 문장에서 유추할 수 있는 것은 상속은 계층도/조직도의 개념으로 이해하는것이 아니라 분류도의 개념으로 이해해야 한다는 것이다.

이게 무슨소리일까? 사진한개로 정리가 가능하다.

상속은 분류도로 이해해라

 

위의 그림을 보면서 생각을 해보자.

 

아버지 아기상어 = new 아들(); 

 

이게 말이 될까? 아들이 생겨서 아기상어라고 칭하는것까지 그럴수있는데 아기상어가 아버지의 역할을 맡고 아버지의 행동(메소드)를 하려고 하고있다...

 

어류 아기상어 = new 상어();

 

상어를 아기상어라 칭하며, 그 아기상어는 어류가 하는 행동(메소드)를 하게 하는것은 논리적으로 이상하지 않다.

이처럼, 상속은 계층도/조직도가 아닌 분류도로 이해하면 된다.

 

 

이와 같이 분류도인 경우로 상속을 이해한다면 하위에 존재하는 것을 상위에 있는 것들의 역할로 대체하는 것이 전혀 문제가 되지 않는다는 것이 감이 올 것이다. 그게 바로 LSP이다.

 

 

 

 

 

4. ISP : 인터페이스 분리 원칙

클라이언트는 자신이 사용하지 않는 메소드에 의존 관례를 맺으면 안된다.

1번에 사용했던 원칙은 SRP였다. 그 예제를 다시 가져와서 생각해보자.

역할과 책임이 넘쳐나는 남자클래스....

우리는 이 역할과 책임이 넘쳐나는 남자클래스를 토막토막 잘라서 SRP원칙을 위배하지 않는선에서 설계해보았었다. 

조금 다른 방법으로 인터페이스를 활용해보자.

 

사진이 좀 깨지지만.... 남자 클래스의 역할을 나누어 인터페이스화 한다는 그림이다.

 

(책의 저자가 한 말이 너무 재밌어서 그대로 가져왔다 ㅋㅋㅋ) 남자 클래스를 토막내는 것이 아니라 자아 붕괴(?) 또는 다중 인격화(?) 시켜서 여자친구를 만날 때는 남자친구 역할만 할 수 있게 인터페이스를 제한하고, 어머니와 있을 때는 아들 인터페이스로 제한하고... 이렇게 하는 것이 바로 인터페이스 분할 원칙의 핵심이다.

 

단일 책임 원칙(SRP)과 인터페이스 분할 원칙(ISP)는 같은 문제에 대한 두가지 해결 방안이라고 볼 수 있다. 상황에 맞는 방안으로 해결하면 된다. (특별한 상황이 아니면 SRP을 적용하는 것이 더 좋은 해결책이라고 한다!!)

 

 

 

 

 

5. DIP : 의존 역전 원칙

고차원 모듈은 저차원 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다.
추상화 된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다.
자주 변경되는 구체(Concrete) 클래스에 의존하지 마라.

 

스노우타이어 클래스에 의존하는 자동차 클래스

자동차 클래스가 스노우타이어 클래스에 의존하고 있다고 가정해보자. 스노우타이어는 겨울에만 사용되고 계절이 바뀌면 일반타이어로 바꾸어 껴주어야 한다. 자동차 클래스는 변화가 잦게 일어나는 클래스에 현재 의존하고 있는 상황이다.

 

 

추상화된 타이어 인터페이스에 의존하는 자동차 클래스

이는 추상화된 타이어 인터페이스에 자동차클래스를 의존시킴으로써 해결할 수 있다. 스노우타이어 클래스, 일반타이어 클래스, 광폭타이어 클래스는 각각 추상적인 타이어 인터페이스를 구현하는 클래스가 되었다.

 

 

 

기존에 스노우 타이어 클래스는 아무것이도 의존적이지 않는 것이었는데,  DIP원칙을 적용하여 리팩토링하면 추상적인 타이어 인터페이스에 의존적이게 되었다. 이로써 의존의 방향이 역전된 것이다. 이것이 DIP원칙이다.

+ Recent posts