객체지향의 사실과 오해

The Essence of Object-Orientation 저자 -조영호-

 

좋은 책이었다. 재미있었다.

최근 열심히 공부해야겠다는 생각을 하고 있었는데, 선임분이 추천해줘서 사서 읽게 됐다.

책을 워낙 더럽게 읽어서 (밑줄을 친다던지, 필기를 한다던지), 구매해서 읽었다.

 

언제 한번 SQL 관련된 책도 한번 봐야 하는데, 사 놓기만 하고 읽지는 않고 있다.

반성하자.

 

목차만 봐도 내용이 얼핏 기억이 날 것 같다. 따라서 목차만 대충 적어놓자.

 

1. 협력하는 객체들의 공동체

 -역할, 책임, 협력.

 -협력 속에 사는 객체

 -자율성

객체지향이란 시스템을 상호작용하는 자율적인 객체들의 공동체로 바라보고, 객체를 이용해 시스템을 분할하는 방법이다.

자율적인 객체란 상태와 행위를 함께 지니며 스스로 자기 자신을 책임지는 객체를 의미한다.

객체는 시스템의 행위를 구현하기 위해 다른 객체와 협력한다. 각 객체는 협력 내에서 정해진 역할을 수행하며 역할은 관련된 책임의 집합이다.

...

객체 지향의 본질은 객체다. 클래스가 아니다.

핵심은 적절한 책임을 수행하는 역할 간의 유연하고 견고한 협력 관계를 구축하는 것이다.

클래스는 협력에 참여하는 객체를 만드는 데 필요한 구현 메커니즘일 뿐이다.

 

2. 이상한 나라의 객체

값객체와 참조객체는 다르다. 참조객체는 엔티티, 즉 식별자를 지닌 전통적인 의미의 객체를 가리키는 용어이다.

 -객체는 상태를 가지며 상태는 변경 가능하다.

 -객체의 상태를 변경시키는 것은 객체의 행동이다.

 -객체는 어떤 상태에 있더라도 유일하게 식별 가능하다.

(위 객체는 모두 엔티티)

버튼을 누른건 사용자지만... 버튼에 따라 어떻게 작동할지는 기계 스스로 결정한다...

객체에 접근할 수 있는 유일한 방법은 객체가 제공하는 행동뿐

 

행동이 상태를 결정한다. 상태를 먼저 정의하고 행동을 정의하는 것이 아니라, 행동을 먼저 정의해야 한다.

협력에 참여하는 훌륭한 객체 시민을 양성하기 위한 가장 중요한 덕목은 상태가 아니라 행동에 초점을 맞추는 것이다.

 

객체지향의 설계는 애플리케이션에 필요한 협력을 생각하고 협력에 참여하는 데 필요한 행동을 생각한 후 행동을 수행할 객체를 선택하는 방식으로 수행한다.

 

객체의 행동은 결국 객체가 협력에 참여하면서 완수해야할 책임을 의미하며. 어떤 책임이 필요한가를 결정하는 과정이 전체 설계를 주도해야 한다. 

 

객체지향은 현실세계의 모방이긴 한데, 추상화가 아니라 은유라고 표현하는 것이 더 어울린다.

객체지향은 현실세계를 은유한다. (의인화)

 

3. 타입과 추상화

현실 세계는 복잡하다. 이 복잡성을 낮출 필요가 있다. 

추상화는 현실에서 출발하되 불필요한 부분을 도려내가면서 사물의 놀라운 본질을 드러나게 하는 과정이라고 할 수 있다.

추상화는 공통점을 취하고 차이점을 버리는 일반화, 세부사항을 버리는 단순화 두가지 측면을 갖는다.

 

개념: 공통점을 기반으로 객체들을 묶기 위한 그릇. 

 

개념을 이용해서 객체를 여러 그룹으로 "분류" 할 수 있다.

개념에는 세 가지 관점이 있다. 심볼(이름), 내연(의미), 외연(각 포함되는 놈들)

 

분류란 객체에 특정한 개념을 적용하는 과정이다.

 

개념은 추상화의 첫 번째 차원인 "일반화"를 적용한 결과다.

 

타입은 결국 개념이다.

타입은 공통점을 기반으로 객체를 묶기 위한 틀이다.

정통적인 데이터 타입은 객체의 타입과 많이 유사하다.

 

1.어떤 객체가 어떤 타입에 속할지 정하는 것은, 객체가 수행하는 행동이다.

어떤 객체들이 동일한 행동을 할 수 있다면 둘은 같은 타입이다.

2.객체의 내부적인 표현은 외부로부터 철저히 감춰진다. 객체의 행동을 가장 효과적으로 수행할 수만 있다면 객체 내부의 상태를 어떤 식으로 표현해도 무방하다.

 

결국 행동이 중요하다.

객체의 행동은 결국 책임을 의미한다고 했다. 결과적으로 동일한 책임을 수행하는 일련의 객체는 동일한 타입으로 볼 수 있다.

객체가 어떤 데이터를 갖는지는 우리의 관심사가 아니다. 동일한 메세지를 수신할 수 있는 놈이기만 하면 된다.

 

훌륭한 객체지향 설계는 외부에 행동만을 제공하고 데이터는 행동 뒤로 감춰야 한다. 흔히 캡슐화라고 한다.

객체를 결정하는 것은 행동이다. 데이터는 단지 행동을 따를 뿐이다.

 

결국 타입은 추상화다. 타입을 이용하면 객체의 동적인 특성을 추상화할 수 있다. 결국 타입은 시간에 따른 객체의 상태 변경이라는 복잡성을 단순화할 수 있는 효과적인 방법이다.

 

클래스와 타입을 혼동하지 말자. 클래스는 타입의 구현 외에도 코드를 재사용하는 용도로도 사용되기 때문에, 클래스와 타입을 동일시하는 것은 수많은 오해와 혼란을 일으킨다.

 

타입을 나누는 것은 객체의 행동이다. 객체를 분류하기 위해 타입을 결정한 후, 프로그래밍 언어를 이용해 타입을 구현할 수 있는 한 가지 방법이 클래스일 뿐이다.

 

4. 역할, 책임, 협력

객체의 책임은 객체가 무엇을 알고 있는가, 무엇을 할 수 있는가로 구성된다.

객체지향에서 책임은 무진장 중요하다.

 

객체가 수행하는 책임의 집합은 객체가 협력 안에서 수행하는 역할을 암시한다.

역할을 대체할 수 있는 객체는 동일한 메시지를 이해할 수 있는 객체로 한정된다.

역할의 개념을 사용해 유사한 협력을 추상화해서 인지 과부하를 줄일 수 있다.

본질적으로 역할은 다른 객체에 의해 대체 가능함을 의미한다.

객체는 역할이 암시하는 책임보다 더많은 책임을 가질 수 있다.

역할이 협력을 추상적으로 만들 수 있는 이유는 역할 자체가 객체의 추상화이기 때문이다.

 

올바른 객체를 설계하기 위해서는 먼저 견고하고 깔끔한 협력을 설계해야 한다.

 

요청과 응답의 흐름 -> 책임 할당 -> 각 객체가 외부에 제공해야할 행동 -> 데이터 고민 -> 클래스 구현 방법 결정.

협력이라는 문맥을 절대 잊지 말자.

 

5. 책임과 메세지.

너무 구체적 메세지와 너무 추상적인 메세지는 둘 다 안좋다.

너무 구체적인 책임을 요구하면, 객체의 자율성이 떨어진다.

 

추상적이고 포과렂ㄱ인 책임은 협력을 좀 더 다양한 환경에서 재사용할 수 있도록 유연성이라는 축복을 내려준다. 그러나 책임은 협력에 참여하는 의도를 명확하게 설명할 수 있는 수준 안에서 추상적이어야 한다.

 

어떻게가 아니라 무엇을 할 지를 명령하라.

 

한 객체의 책임은, 객체가 수행하는 행동이며, 이 행동이 실행되는 때는 다른 객체의 "메세지" 가 전달돼야 한다. 

 

어떤 객체에게 메시지를 전송하면 결과적으로 메시지에 대응하는 특정 메서드가 실행된다.

메시지는 어떻게 수행될 것인지는 명시하지 않는다. 어떤 메서드를 선택할 것인지는 전적으로 수신자의 결정에 좌우된다.

 

메시지를 수신한 객체가 실행 시간에 메서드를 선택할 수 있다는 사실은 다른 프로그래밍 언어와 객체지향 프로그래밍 언어를 구분 짓는 핵심적인 특성 중 하나다.

 

다형성이란 서로 다른 유형의 객체가 동일한 메시지에 대해 서로 다르게 반응하는 것을 의미한다.

기본적으로 다형성은 동일한 역할을 수행할 수 있는 객체들 사이의 대체 가능성을 의미한다.

 

책임-주도 설계 방식에서는 협력에 참여할 객체를 결정하기 전에, 협력에 필요한 메세지를 먼저 결정한다.

(어떤 메세지가 필요할까? -> 이제 이건 누가 수신하고 처리하지?)

 

객체가 다른 객체의 상태를 묻는다는 것은 메시지를 전송하기 이전에 객체가 가져야 하는 상태에 관해 너무 많이 고민하고 있다는 증거다.

결과적으로 묻지 말고 시켜라. 

 

인터페이스와 구현의 분리

 

소프트웨어는 항상 변경되기 때문.

 

캡슐화 (정보은닉)

 

상태와 행위의 캡슐화

 객체는 상태와 행동을 하나의 단위로 묶는 자율적인 실체. 객체는 상태와 행위를 한 데 묶고, 외부에서 반드시 접근해야만 하는 행위만 골라 공용 인터페이스를 통해 노출한다. 따라서 데이터 캡슐화는 인터페이스와 구현을 분리하기 위한 전제조건이다.

 객체지향에서는 데이터와 프로세스를 객체라는 하나의 틀 안으로 함께 묶어 놓음으로써 객체의 자율성을 보장한다.

사적인 비밀의 캡슐화

 공용 인터페이스만 노출.

 

객체의 외부와 내부를 명확하게 구분하라. 그러면 설계가 단순하고 유연하고 변경하기 쉬워질 것이다.

 

6. 객체 지도

안정적인 도메인 모델을 기반으로, 기능을 구현하라.

기능을 정리하는데 유스케이스 모델을 사용할 수 있다.

 

도메인 모델 -> 안정적인 구조 제공.

안정적인 구조를 기반으로 기능 개발 필요.

 

책임 할당의 기본 원칙은 책임을 수행하는 데 필요한 정보를 가진 객체에게 그 책임을 할당하는 것이다.

 

7. 함께 모으기

 

서브클래스와 슈퍼 클래스.

 

구조적 순응: 서브클래스가 슈퍼클래스와 같은 속성 갖고 있음 

행위적인 순응을 흔히 리스코프 치환원칙이라고 한다.

(Person 의 getAge() , Employee 의 getAge())

서브타이핑과 서브클래싱은 다르다.

서브클래스가 슈퍼클래스를 대체할 수 있는 경우를 서브타이핑이라고 하고, (설계의 유연성을 위함)

대체 못하는 경우를 서브 클래싱이라고 한다. (코드 중복 제거와 재사용 목적)

 

 

 

중요해보이는 것만 한번 적어보려다가 난리가 났다.

위의 내용은 책과 완전히 일치하진 않는다. 내가 멋대로 해석해서 적은 내용도 있다.

 

그리고 이해가 안가는 부분이 많을 수도 있다.

내가 다시 봤을 때, 연상 될 수 있는 방식으로 글을 적었기 때문이다.

 

하여간 이렇게 책 한권을 읽었다.

좋은 책이었다. 감사합니다 영호님.

블로그 이미지

맛간망고소바

,

일단 IntelliJ 를 이용해서 서버를 구축할 것이다.

Hibernate JPA 와, MyBatis 둘 중에 하나를 선택해야 하는데.

 

이번 기회에 MyBatis 와 조금 더 친밀해져 보려고 한다.

 

일단 둘의 차이점을 알고 가보자.

 

JPA 란 무엇인가?

 

JPA(Java Persistent API)

  JPA는 여러 ORM 전문가가 참여한 EJB 3.0 스펙 작업에서 기존 EJB ORM이던 Entity Bean을 JPA라고 바꾸고 JavaSE, JavaEE를 위한 영속성(persistence) 관리와 ORM을 위한 표준 기술이다. JPA는 ORM 표준 기술로 Hibernate, OpenJPA, EclipseLink, TopLink Essentials과 같은 구현체가 있고 이에 표준 인터페이스가 바로 JPA이다.

  ORM(Object Relational Mapping)이란 RDB 테이블을 객체지향적으로 사용하기 위한 기술이다. RDB 테이블은 객체지향적 특징(상속, 다형성, 레퍼런스, 오브젝트 등)이 없고 자바와 같은 언어로 접근하기 쉽지 않다. 때문에 ORM을 사용해 오브젝트와 RDB 사이에 존재하는 개념과 접근을 객체지향적으로 다루기 위한 기술이다.

 

https://blog.woniper.net/255

 

[JPA] JPA란 무엇인가?

JPA란 무엇인가? JavaSE 환경에서 JPA 설정 및 CRUD JavaEE 환경(Spring)에서 JPA 설정 및 CRUD @OneToOne, 1:1 관계 매핑 @OneToMany / @ManyToOne, 1:N / N:1 관계 매핑 @ManyToMany, N:M 관계 매핑 Entity 객체..

blog.woniper.net

한글로 풀어 쓰면, 자바 영속성 API(Application Programming Interface) 이다. API 라는 것은 말 그대로, 어플리케이션 프로그램이 다른 프로그램을 사용할 수 있게 해주는 인터페이스를 뜻한다. 

 즉 "자바로 영속성을 다루는 프로그램"의 "인터페이스"를 제공하는 것이 바로 JPA 다.

이 것은 말 그대로 "인터페이스" 이므로, 이를 구현하는 것에는 여러가지 프로그램이 있다. 가장 대표적인 것 중 하나가 바로 Hibernate다.

 

자. 간단히 정리하자면. JPA 는 결국 인터페이스이고, 이 인터페이스를 구현한 것 중 하나가 하이버네이트이다.

이렇게 따지면 MyBatis도 JPA 를 구현한 것 아닌가?? 할 수 있겠지만. JPA 는 풀 네임만 보면 오해의 여지가 있지만, 사실 ORM 을 위한 표준 기술을 의미한다. 

 ORM 은 Object Relational Mapping 의 약자다. 

 

그럼 MyBatis는 무엇이냐? Sql Mapper다. 이 두 개의 차이점은 간단히 말하자면,

 

ORM 은 Object를 기반으로 DB 가 생기고,

SQL Mapper는 DB 구축은 해 놓고, 그 DB에서 꺼내온 데이터를 Object에 꾸겨 넣는 것이다.

 

 하여간 MyBatis를 사용하기로 하고, 한번 IntelliJ를 이용해 간단히 스프링 부트 프레임워크를 구축해보자.

 

IntelliJ 에선 그 전에 살펴봤던, Spring initializer를 간단히 사용할 수 있게 해준다.

 

 

대충 원하는대로 서버 정보를 입력 해준다.

 

웹 서버이기 때문에 Spring Web Server 를 체크 해준다.

DB 설정

DB 는 간단히 H2 를 사용하자. 나중에 이 부분은 MariaDB 로 바꿀 것이다. (mySql)

H2 란 무엇인가?

 

H2DB

H2DB는 자바 기반의 오픈소스 관계형 데이터 베이스 관리 시스템(RDBMS )입니다.

H2DB는 서버(Server) 모드임베디드(Embedded) 모드인메모리 DB 기능을 지원합니다. 물론 디스크 기반 테이블을 또한 생성할 수 있습니다.

또한 브라우저 기반의 콘솔모드를 이용할 수 있으며, 별도의 설치과정이 없고 용량도 2MB(압축버전) 이하로 매우 저용량 입니다. DB자체가 매우 가볍기 때문에 매우 가볍고 빠르며, JDBC API 또한 지원하고 있습니다.

SQL 문법은 다른 DBMS들과 마찬가지로 표준 SQL의 대부분이 지원됩니다.

이러한 장점들 때문에 어플리케이션 개발 단계의 테스트 DB로서 많이 이용됩니다.



출처: https://dololak.tistory.com/285 [코끼리를 냉장고에 넣는 방법]

인 메모리로 사용할 시, 서버가 종료하면 자동으로 데이터도 사라진다. 메모리 상에만 데이터가 떠 있게 된다.

 

캐시 서버로도 쓰일 수 있겠지만, 그 방면에서는 key-value 형식의 noSql 강자인 redis 가 있으므로 포지션이 애매해진다.

현재 가장 적합한 포지션은 "테스트용 경량 RDBMS" 정도다.

 

Developer tool 에서는 간단히 Lombok plugin 만 설정해 두었다. Lombok 에 대해서는 따로 언급하지 않겠다.

 

이제 필요한 디펜던시들이 설정되고, spring initializer 가 이 설정을 기반으로 pom.xml 을 만들어주고, 필요한 소스 파일 및 폴더 구조를 모두 만들어 준다. 조금 기다리면 메이븐 기반으로 라이브러리들이 설치된다.

 

이제 mybatis 와 h2로 기본적인 설정을 진행 해보자.

 

일단 dataSource 설정을 미리 해주어야 한다. DataSource 설정이란 무엇인가?

 

https://gmlwjd9405.github.io/2018/05/15/setting-for-db-programming.html

불러오는 중입니다...

 

여기에 아주 자세히 설명 돼있다.

 

이 링크를 타면 만나는 DAO, DTO, Entity Class 의 차이도 한 번 읽으면 좋을 것 같다.

 

https://gmlwjd9405.github.io/2018/12/25/difference-dao-dto-entity.html

불러오는 중입니다...

가장 핵심은 이것이다.

 

DAO 는 Database 에 Access 할 때 사용하는 Object다. Spring 기준으로 보면 보통 @Repository 가 붙은 놈들이다.

DTO 는 말 그대로 Data를 주고 받을 때 사용하는 Object이며

Entity Class 는 DB 와 Data를 직접 주고 받을 때 사용하는 놈이다.

 

왜 DTO와 Entity Class를 나누는가?

 

-View Layer와 DB Layer의 역할을 철저하게 분리하기 위해서

-테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 끼치게 되는 반면 View와 통신하는 DTO 클래스(Request / Response 클래스)는 자주 변경되므로 분리해야 한다.

-Domain Model을 아무리 잘 설계했다고 해도 각 View 내에서 Domain Model의 getter만을 이용해서 원하는 정보를 표시하기가 어려운 경우가 종종 있다. 이런 경우 Domain Model 내에 Presentation을 위한 필드나 로직을 추가하게 되는데, 이러한 방식이 모델링의 순수성을 깨고 Domain Model 객체를 망가뜨리게 된다.

또한 Domain Model을 복잡하게 조합한 형태의 Presentation 요구사항들이 있기 때문에 Domain Model을 직접 사용하는 것은 어렵다.
즉 DTO는 Domain Model을 복사한 형태로, 다양한 Presentation Logic을 추가한 정도로 사용하며 Domain Model 객체는 Persistent만을 위해서 사용한다.

 

즉, DB 와 직접 통신하는 놈은 최대한 군더더기 없이 유지하는 것이 좋다.

데이터 전달 상, DB 통신과는 전혀 상관없는 필드와 메서드들이 덕지덕지 붙을 수 있기 때문에 Entity Class와 DTO 클래스를 분리한다.

DTO 클래스는 상~당히 Entity Class 와 유사하다.

 

다시 DataSource 로 돌아가보자.

 

위에 DAO 보이는가? 

DAO 는 Data Access Object가 Database를 만나기 위해 거쳐야 하는 두 계단이 있다.

JDBC Template과 JDBC Driver다.

 

아래의 설명은 Datasource 링크의 내용이다.

 

# DataSource 란?
DataSource는 JDBC 명세의 일부분이면서 일반화된 연결 팩토리이다. 즉 DB와 관계된 connection 정보를 담고 있으며, bean으로 등록하여 인자로 넘겨준다. 이 과정을 통해 Spring은 DataSource로 DB와의 연결을 획득한다.

DataSource는 JDBC Driver vendor(Mysql, Oracle 등) 별로 여러가지가 존재한다.

DataSource가 하는 일
 - DB Server와의 기본적인 연결
 - DB Connection Pooling 기능 (* 아래 참고)
 - 트랜젝션 처리


DataSource의 구현 예시
 - BasicDataSource (선택!)
 - PoolingDataSource
 - SingleConnectionDataSource
 - DriverManagerDataSource

 

DataSource 는 커넥션 정보를 담는다. 즉 DB Server와의 기본적인 연결을 담당한다.

 

 

따라서 우리는 이 Datasource 설정을 해줄 필요가 있는 것이다. 

mybatis 와 h2를 이용하기 위해서 아래의 링크들을 참고할 것이다.

"참고" 이기 때문에, 아래의 링크와 다른점도 많다. (다른점이 더 많을 수도 있다.)

 

http://tech.javacafe.io/2018/07/31/mybatis-with-spring/

 

Spring Boot에서 mybatis 설정하기

스프링 프로젝트를 진행할때마다 DB와의 연동은 필수적으로 들어가게 된다. 최근에는(드디어) JPA를 이용해 프로젝트를 많이 진행하지만, 여전히 SQL을 이용한 직관적인 매핑방식도 많이 사용되고 있다. 단순한 프로젝트라면 SpringJdbc를 이용해서도 충분히 SQL을 구성할 수 있다. 하지만 실무환경에서는 다양한 경우의수가 발생하고 이에 따른 SQL도 함께 변경되어야 하고, 이러한 복잡한 SQL이 필요한 지점에서 MyBatis의 동적 SQL관리방법은 프로

tech.javacafe.io

 

 

https://www.technicalkeeda.com/spring-tutorials/spring-4-jdbctemplate-annotation-example

 

Spring 4 JdbcTemplate Annotation Example

In this tutorial, we will learn how to connect to the database and execute CRUD SQL queries using Spring 4 JdbcTemplate. Java base configuration is used to load the JdbcTemplate, here we are not using XML base configuration for JdbcTemplate.

www.technicalkeeda.com

왜 굳이 이렇게 두 개를 참고하냐면,

개인적인 공부를 위해서 Datasource 설정을 두 가지 방법으로 해볼 생각이기 때문이다.

 

첫 번 째로, spring boot 의 자동설정 기능을 이용해본다.

두 번 째로, @Configuration 으로 Datasource Bean 을 JAVA 코드로 만들어 본다.

 

 

음... 따라해보자.

일단 spirng boot 기본 설정을 이용해 datasource 설정을 해준다.

application.properties 파일에 다음과 같이 입력 해주자.

 

spring.datasource.url=jdbc:h2:mem:test
spring.datasource.username=wp
spring.datasource.password=password123
spring.datasource.driver-class-name=org.h2.Driver

 

비밀번호는 테스트이므로 아무거나 입력했다. 나중에는 바꿔줘야 한다.

 

예시

 

 

H2는 인메모리 DB로 애플리케이션이 동작하는 경우에만 DB가 함께 동작한다. Spring이 시작하는 시점에 scheme이 생성되도록 /src/main/resource/scheme.sql을 생성하여 주고 아래에 다음과 같이 table 생성 SQL과 예제데이터를 추가하여 주자.

 

mybatis 설정 관련 첫 번째 링크에서 Spring이 시작되는 시점에 DB의 scheme 을 생성하기 위해 /resource 아래에 scheme.sql 을 생성해 준다고 돼있다. 링크를 타고 가서 스프링 공식 문서를 봐보자.

 

Spring Boot can automatically create the schema (DDL scripts) of your DataSource and initialize it (DML scripts). It loads SQL from the standard root classpath locations: schema.sql and data.sql, respectively. In addition, Spring Boot processes the schema-${platform}.sql and data-${platform}.sql files (if present), where platform is the value of spring.datasource.platform. This allows you to switch to database-specific scripts if necessary. For example, you might choose to set it to the vendor name of the database (hsqldb, h2, oracle, mysql, postgresql, and so on).

 

공식 문서에 의하면, Spring Boot 는 자동으로 스키마를 생성하고, 데이터를 생성할 수 있다. 

기본 루트 클래스 패스 (/src/main/resources/schema.sql 이겠지?) 위치에 있는 scheme.sql 에 기입된 DDL scripts 를 실행해 schema 를 생성하고, data.sql 에 기입된 DML scripts 를 실행해 "데이터" 를 생성한다.

 DDL 과 DML 의 차이는 여기서는 설명하지 않겠다. (다들 알겠지.)

 

 

일단 DDL 을 이용해 schema.sql 을 작성 해야 하는데 ,먼저 schema.sql 파일을 생성하자.

 

schema.sql

 

 

여기서 DDL 을 사용해 scheme.sql 안을 채워야 하는데.

 

https://www.fun-coding.org/mysql_basic3.html

 

데이터베이스 기본 (MySQL): SQL DDL(Data Definition Language) 이해 및 실습 - 잔재미코딩

3.2.2 테이블 삭제¶ 기본 문법 (DROP TABLE 테이블명)mysql> DROP TABLE [IF EXISTS] 테이블명; IF EXISTS 옵션은 해당 데이타베이스 이름이 없더라도 오류를 발생시키지 말라는 의미

www.fun-coding.org

너무 좋은 링크가 있어서, 여길 참고해서 만들어 보도록 하겠다.

 

링크의 내용과 여러 Goolgle 검색을 토대로 DDL 을 작성 해준다. 덤으로 DML 도 살짝 얹었다.

 

CREATE TABLE TEXT_DIARY(
    ID INT PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(20) NOT NULL,
    CONTENT TEXT NOT NULL,
    DATE DATE DEFAULT CURRENT_DATE,
    CREATE_DATE DATETIME DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO TEXT_DIARY (NAME, CONTENT) VALUES ('오늘의 일기', '오늘은 공부를 했다.')

 

일단 간단한 테스트 TABLE 을 만들어 보도록 했다.

TEXT만 입력 가능한 간단한 도메인을 설정 해보자. 

 

TEXT_DIARY 의 PRIMARY_KEY 는 ID 이며  INT 타입으로, 자동으로 증가하는 값을 가진다,

이름을 갖고 있으며 20자 이내여야 한다, 내용은 길게 들어갈 수 있으므로 적당히 TEXT 로 해놓는다,

CREATE_DATE 와 DATE 를 갖는다. 이 둘을 구분한 이유는 상식적으로 일기는 항상 당일에 작성하지는 않기 때문이다.

 

아직은 테스트 단계이기 때문에 전체적인 틀을 잡는다기보다는 간단한 테스트용 테이블을 위와 같이 만들어 보았다.

 

예시

링크에서 시키는대로 엔트리 포인트가 되는 클래스에다가 @MapperScan 을 붙여준다.

 

mapper를 스캔할 수 있도록 설정하자. SpringBoot Configuration 파일상단에 아래와 같이 프로젝트 패키지의 경로를 넣자. 이렇게 하여 프로젝트 하위에 존재하는 Mapper들을 빈으로 등록할수 있게 된다.

 

@MapperScan을 붙인 모습

링크를 보면 모델의 type-alias 를 사용하기 위한 application.properties 파일 수정이 있지만, 그 부분은 제외하도록 하자.

 

DML 을 이용해 나름 데이터를 입력까지 했으니, 일단 꺼내서 확인해보자.

Entity class를 만들자. 이를 위해 package를 새로 따서, 그 안에 TextDiary 라는 Entity Class를 만들 것이다.

 

 

 

그리고 데이터를 입력 해준다.

 

package com.wpapp.voice_diary_server.model;

import lombok.Data;

import java.util.Date;

@Data
public class TextDiary {
    private Integer id;
    private String name;
    private String content;
    private Date date;
    private Date createDate;
}

 

대충 요런 형태로 만들었다.

 

그리고 DAO 역할을 해줄 mapper 인터페이스와(repository), 이 DAO 를 다루는 서비스, 그리고 그 서비스를 다루는 Rest Controller 를 만들 것이다.

 

구조는 이렇게 된다.

 

Rest Controller - 서비스 - DAO(Repository and mapper)

 

결과적으로 다음과 같은 구조가 된다.

 

 

 

TextDiaryMapper 는 다음과 같다.

 

package com.wpapp.voice_diary_server.mapper;

import com.wpapp.voice_diary_server.model.TextDiary;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface TextDiaryMapper {
    List<TextDiary> findAll();
}

 

왜 findAll() 만 있냐면, 당장 schema.sql 에서 넣은 데이터를 확인하고 싶기 때문이다. 

이름이 왜 findAll() 인가? 그냥 말 되게 하면 되는데 getAll() 이던 findAll() 이건...

하지만 프로그래머는 convention 을 따르는 것이 중요하기 때문에, 다음에 이 DAO method 의 관례에 대해 알아보자.

 

 

그리고 TextDiaryMapper.xml 을 만들어 준다.

 

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.wpapp.voice_diary_server.mapper.TextDiaryMapper">
    <select id="findAll" resultType="com.wpapp.voice_diary_server.model.TextDiary">
        SELECT
        ID, NAME, CONTENT, DATE, CREATE_DATE as createDate
        FROM TEXT_DIARY
    </select>
</mapper>

이 파일은 다음과 같은 위치에 있다.

 

이 mapper xml 파일을 제대로 읽어들이려면, application.properties 파일에 이 mapper xml 파일들의 위치를 설정해주어야 한다.

 

spring.datasource.url=jdbc:h2:mem:test
spring.datasource.username=wp
spring.datasource.password=password123
spring.datasource.driver-class-name=org.h2.Driver


mybatis.mapper-locations=classpath:/mapper/*.xml

그러면 application.properties 의 모양새는 위처럼 된다.

(classpath: 는 resources 다.)

 

 

TextDiaryService 는 다음과 같다.

 

package com.wpapp.voice_diary_server.service;

import com.wpapp.voice_diary_server.mapper.TextDiaryMapper;
import com.wpapp.voice_diary_server.model.TextDiary;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class TextDiaryService {
    @Autowired
    private TextDiaryMapper textDiaryMapper;

    public List<TextDiary> findAll() {
        return textDiaryMapper.findAll();
    }
}

 

솔직히 왜 있는줄 모르겠다.

이것 또한 관례를 따른 것인데, 이 관례도 워낙 옛날에 듣고 따라하는 것인지라 나중에 자세히 알아보도록 하자.

(서비스 - 리포지토리 분리)

 

-----

나중에 할 것

1. repository method 명명 관례(혹은 dao method)

2. service, repository 등의 구분 이유 및 관례

------

 

마지막으로 RestController

 

package com.wpapp.voice_diary_server.controller;

import com.wpapp.voice_diary_server.model.TextDiary;
import com.wpapp.voice_diary_server.service.TextDiaryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/text-diaries")
public class TextDiaryRestController {

    @Autowired
    private TextDiaryService textDiaryService;

    @GetMapping
    public List<TextDiary> findAllTextDiary() {
        List<TextDiary> diaries = textDiaryService.findAll();
        return diaries;
    }
}

 

이렇게 일사천리로 코드를 작성하고 localhost:8080/text-diaries 에 들어가보면 어찌 되는가?

 

시간이 이상하게 나온다.

 

...??

 

타임존이 다르다.

 

Date 가 JSON 으로 바뀌는 과정에서 뭔가 이상한 타임존의 변화가 있는 것일까?

일단 Date 를 LocalDate 와 LocalDateTime 으로 바꾸면 당장은 제대로 작동하게 된다.

 

TextDiary를 다음과 같이 바꾸었다.

 

package com.wpapp.voice_diary_server.model;

import lombok.Data;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
public class TextDiary {
    private Integer id;
    private String name;
    private String content;
    private LocalDate date;
    private LocalDateTime createDate;
}

 

이러니 제대로 작동한다. 왜 이러는 걸까?

당장은 다음의 과제로 남겨야 할 것 같다. 졸리기 떼문이다.

 

https://www.baeldung.com/java-set-date-time-zone

 

Set the Time Zone of a Date in Java | Baeldung

Learn how to set the time zone of a date in Java 7, Java 8 and Joda-Time

www.baeldung.com

일단 위의 링크와

 

https://www.baeldung.com/jackson-serialize-dates

불러오는 중입니다...

위의 링크를 남기고, 다음에 한번 왜 이러한 상황이 발생 했는지 정리하도록 하자.

 

 

이번에는 "초기 설정" 이기 때문에 세세히 정리를 해두었다. 

초기 설정은, 처음에 한 뒤로 "프로젝트를 다시 시작하지 않는 이상" 다시 안 할 가능성이 높기 때문에,

그만큼 덜 해보게 되고, 그만큼 한 번 하고나면 잊고 끝나기 마련이다.

 

따라서 초기 설정을 세세히 정리하고, 다시 복습하고자 요래 쓸데없이 장황하게 정리를 해놓았다.

 

미래의 나에게 도움이 되길 빈다.

블로그 이미지

맛간망고소바

,

이제부터 스프링 부트 서버를 "공부 및 제작" 할 것이다.

 

스프링부트는 차세데 스프링 프레임워크 이다. 스프링 부트는 개발 방식을 단순화한다.

스프링 부트는 탄탄하고 확장성 좋은 스프링 앱을 개발하는 방향으로 베스트 프랙티스를 따르도록 인도하는 독자적인 기술의 결집체이다.

 

아주 간단한 스프링 웹 애플리케이션도 J2EE 스택 및 스프링 프레임워크에 관한 몇 가지 개발 규칙은 지켜야 한다.

 

웹 아카이브(WAR) 파일 내부 구조는 다음과 같다.

 - WEB-INF 폴더는 반드시 필요하고, 하위에 lib, classes 폴더를 둔다.

    - lib 폴더엔 서드파티 라이브러리,

    - classes  폴더엔 웹 애플리케이션 클래스를 넣는다.

- 필요시 JSP, HTML, CSS, 이미지, 자바스크립트 파일을 넣는다.

- web.xml 파일에 디스패처 서블릿을 선언한다.

- <서블릿명>-servlet.xml 형식의 스피링 빈 구성 파일이 필요하다.

 

war 파일은 유틸리티로 패키징한다.

war 파일은 톰캣, 제티, 제이보스, 웹스피어 같은 "애플리케이션 서버 또는 컨테이너" 에 올려놓고 실행하거나, J2EE 앱을 배포하는 전용 서버를 따로 운용하기도 한다.

 

-- 웹 아카이브의 구조 및, 웹 아카이브로 패키징 해야 한다는 것, 그리고 이 war 파일을 실행하는 데 필요한 것. --

 

몇빈 선언 시점에 스프링 XML 스키마를 올바르게 교정하는 STS 같은 IDE 부터, 요맨 같은 외부 도구에 이르기까지, 구조를 바로 잡고 틀에 박힌 구성코드를 일일이 입력하지 않게 해주는 도구는 여럿 있다.

 

package com.example.wphomepage;

import com.example.wphomepage.journal.Journal;
import com.example.wphomepage.journal.mapper.JournalMapper;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class WpHomepageApplication {

    @Bean
    InitializingBean saveData(JournalMapper repo) {
        return () -> {
            repo.insert(new Journal("스프링부트 입문", "난 언제나 다시 복습하지", "05/26/2019"));
            repo.insert(new Journal("스프링부트 입문", "난 언제나 다시 복습하지", "05/26/2019"));
        };
    }

    public static void main(String[] args) {
        SpringApplication.run(WpHomepageApplication.class, args);
    }

}

 

 옛날에 작성했던 코드를 붙였다. 5월 26일날 복습했던 기본 코드다.

지금 다시 복습하니 3개월 만에 다시 보는 꼴이다.

 

여기서 public static void main 함수 부분을 보자.

 

SpringApplication.run 부분을 찾을 수 있다.

 

-SpringApplication 은 스프링 앱을 간편히 초기화하는 특별한 클래스이다. 

-스프링 부트로 애플리케이션을 개발하면 XML 구성 파일이 거의 필요 없다.

-여기서는 사용되지 않았지만, SpringApplicationBuilder 라는 연결형 빌더 API 가 있는데, 여러 앱 컨텍스트를 계층화할 수 있다.

앱을 좀 더 세세히 다룰 수 있는 장치라고 보면 된다.

 

 

-스프링 부트는 '독자적인' 기술이므로, 스프링 부트를쓰면 웹 애플리케이션이든 단독형(standalone) 앱이든 어떤 타입의 앱이더라도 올바르게 개발할 수 있다.

-스타터폼으로 의존체를 알기 쉽게 구성, 관리할 수 있다. 가령 웹 애플리케이션을 개발할 때는 메이븐 폼 또는 그레이들 빌드 파일에 spring-boot-starter-web 의존체를 추가한다.

 

 

일단 거두절미하고 스프링 부트 CLI 를 설치해보자.

설치하는 방법은 여러가지다. 그 중 sdkman 을 이용해 설치하는 방법은 다음과 같다.

 

curl -s get.sdkman.io | bash

 

===============

CURL

curl 은 command line 용 data transfer tool 이다. download/upload 모두 가능하며 HTTP/HTTPS/FTP/LDAP/SCP/TELNET/SMTP/POP3 등 주요한 프로토콜을 지원하며 Linux/Unix 계열 및 Windows 등 주요한 OS 에서 구동되므로

여러 플랫폼과 OS에서 유용하게 사용할 수 있다. 또 libcurl 이라는 C 기반의 library 가 제공되므로 C/C++ 프로그램 개발시 위의 protocol 과 연계가 필요하다면 libcurl 을 사용하여 손쉽게 연계할 수 있다.

libcurl은 PHP, ruby, PERL 및 여러 언어에 바인딩되어 있으므로 사용하는 언어나 개발환경에 맞게 libcurl 을 사용할 수 있다.

 

https://www.lesstif.com/pages/viewpage.action?pageId=14745703

 

curl 설치 및 사용법 - HTTP GET/POST, REST API 연계등

curl 은 command line 용 data transfer tool 이다. download/upload 모두 가능하며 HTTP/HTTPS/FTP/LDAP/SCP/TELNET/SMTP/POP3 등 주요한 프로토콜을 지원하며 Linux/Unix 계열 및 Windows 등 주요한 OS 에서 구동되므로

www.lesstif.com

여기에 설명이 아주 잘 돼있다.

 

==============

 

source "$HOME/.sdkman/bin/sdkman-init.sh"

 

여기까지가 sdkman 을 설치하는 과정이고, 버전을 확인하고 싶으면 sdk version 을 입력 해본다.

 

sdk install springboot 

이렇게 하면 스프링 부트 CLI 설치가 진행된다.

 

spring --version 으로 spring CLI 버전을 확인해 보자.

 

 

홈브루 라는 유닉스라이크 운영체제 옵션이 있다. 본래는 macOS 사용자가 유닉스/리눅스 세상에서 빠진 도구를 설치하는 용도로 개발됐다.

홈브루로도 설치할 수 있으나, 일단 넘어가자. sdkman 으로 설치 했으니까.

 

 

스프링 부트 CLI 는 단순히 앱 실행뿐만 아니라 필요한 폴더 구조를 초기화하고 자동 생성하는 용도로도 쓰인다.

 

아주 최소한의 기본 프로젝트를 만드려면 다음과 같이 입력하면 된다.

 

spring init --build gradle myapp

 

--build gradle 옵션을 빼면 기본적으로 메이븐 프로젝트를 만든다.

 

spring init 명령어를 기억하자.

 

 

--여기까지 다시 복습 해보자.

SpringApplication은 스프링 앱을 간편히 초기화하는 특별한 클래스이다.

curl 은 command line용 data transfer tool 이다.

spring init 으로 스프링 부트 프로젝트의 뼈대를 생성할 수 있다.

---

 


컴파일, 테스트 ,빌드 작업에 쓰는 메이븐과 그레이들은 스프링 부트에서도 쓸 수 있다.

-메이븐과 그레이들은 컴파일, 테스트, 빌드 작업에 쓰인다.

 

메이븐을 기준으로, 스프링 부트 앱에 꼭 들어가는 폼 파일을 확인해 보자.

 

<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>wp-homepage</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>wp-homepage</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

 

위에서부터 내려가보자.

parent 안에 spring-boot-starter-parent 부분을 보자.

아티팩트를 선언했는데, 앱 실행에 필요한 라이브러리는 모두 여기에 담겨있다.

스프링 부트 시동에 꼭 필요한 스프링 프레임워크(spring-core) , 스프링 테스트 (spring-test) 는 모두 여기에 있다.

이 parent 상위 폼은 반드시 필요하다.

 

생략된 그 아래 부분인,

스타터 폼 섹션에는 실제로 갖다 쓸 스프링 부트 기능에 해당하는 의존체가 나온다.

 

스타터 폼은 앱에 필요한 의존체를 저눕 알아서 가져오므로 딱 하나만 넣는다. 가령 웹 애플리케이션 프로젝트면 spring-boot-starter-web 하나만 있으면 된다.

 

자 이제 spring-init 명령어는 잠시 잊고,

외부 도구에 눈을 돌려보자.

 

스프링 이니셜라이저라는 놈이 있는데, 피보탈 웹 서비스가 운영하는 사이트에 가면 자세한 정보를 얻을 수 있다.

 

원하는대로 골라 담아, 전체 폴더 구조가 고스란히 압축된 zip 파일을 다운받을 수 있게 해준다.

 

이 스피링 이니셜라이저를 curl 로 실행할 수도 있다.

 

세 번 째로는 스프링 도구모음(STS) 를 이용하는 방법이 있다.

STS는 Spring Tool Suite, 스프링 도구 모음의 약자다.

 

이클립스를 쓸 경우 해당 STS 플러그인을 설치하거나 해서 사용할 수 있다.

 

정리---

1.  spring CLI 를 설치한 후, spring-init 

2. 스프링 이니셜라이저를 사용하기

3. STS 플러그인을 설치하고 활용

 

등등 여러가지 방법으로 프로젝트를 생성할 수 있다.

 

intelliJ 같은 걸 설치하면 기본적으로 3번이 설치돼있는 경우가 많다.

 

3번을 이용해서 바로, 처음부터 프로젝트를 진행해보도록 하자.

 

 

 

블로그 이미지

맛간망고소바

,

쓸 데 없이 오래 걸렸다. 오래 걸린 이유가 있다.

IntelliJ, JetBrain은 나에게 똥을 줬다. IntelliJ Education 은 아주 쓰레기였다. 툭하면 강제 종료를 유발했다.

 

결국 껐다 켰다를 반복하며 학습을 계속하다, 종래엔 프로그램이 아예 작동하지 않았다.

프로그램을 끄고 다시 켜도 그 지경이었다. 캐쉬도 삭제해보고, 프로그램을 삭제하고 다시 설치해보기도 했다.

 

결과는 똑같았다. 이 개똥같은 놈.

 

그래서 공부 방법을 바꿨다. 차라리 책을 샀으면 편하기라도 했을텐데...

 

일단 1차 공부는, HyperSkill 에서 하기로 했다.

 

아름답지 않은가? 일단 모두 끝냈다.

간단히 정리도 했다.

 

자바를 주로 쓰다보니 자바를 중심으로 차이점을 찾게 되더라. 일단 재미는 있었다.

문제는 좀 쓸 데없이 귀찮았다. 굳이 이렇게까지 해야 하나 싶었다. (차라리 책을 볼걸)

 

 

변수

 

var 는 변하는, val 는 변하지 않는 변수를 선언할 때 쓰인다.

코틀린은 타입을 중요하게 생각하는 언어다. 타입 추론을 아주 잘 해주는 언어이기도 하다.

 

type

Bit width

Double

64

Float

32

Long

64

Int

32

Short

16

Byte

8

 

대충 사이즈는 위와 같다.

 

 

"Kotlin 에서 Character 는 Number 가 아니다."

자바는 primitive type, reference type 을 두고 있다. 그리고 확실히 구분해서 사용해야 한다.

하지만 코틀린은 그냥 모두 object로 바라볼 뿐이다.

 

 

String 

자바와 상당히 유사하다.

 

  • .first() returns the first symbol of the string.
  • .isEmpty() returns true, if the string has no symbols, otherwise, it returns false.
  • .drop(n) removes n first symbols from the string and returns the resulting string.
  • .reversed() returns the reversed string.
  • .toInt() converts the string to an integer and returns it.

등의 함수들을 제공한다.

자바는 charAt 등을 이용해 String 의 일부 '문자' 값을 읽을 수 있다.

 

코틀린의 String 에선 그냥 배열의 원소에 접근하듯이 접근하면 된다.

 

 

Val greeting = “Hello”

println(greeting.first())   // 'H'

println(greeting.last())    // 'o'

println(greeting.lastIndex) // 4

 

이런 것들도 기억 해두자.

 

코틀린에서 문자열과 숫자는 "불변" 이다. 그 중 문자열 같은 경우..

 

 

val str = "string"

str[3] = 'o' // an error here!

 

var str = "string"

str = "strong”//It’s ok

 

이런 특징을 갖고 있다.

 

중간 값만 못 바꾼다. 그냥 새로운 값을 만들어서 넣어야 한다. 이것이 바로 불변이란 것이다.

 

 

문자열 템플릿

 

  1. Use string templates to insert values of variables into a string like "this string has$value".
  2. If you would like to get an arbitrary expression - use curly braces like "The length is${str.length}".

이렇게 쓸 수 있다.

 

 

Range ( 범위 )

Kotlin 에서는

 

val within = a <= c && c <= b

요런 상황을 대신할

Ranges 라는 개념(?) 제공한다.

 

val within = c in a..b

 

이렇게, 그대로범위 지정하 ㄹ수 있다.

A~b 까지 모두 포함이다. 기억하자. 

 

val withinExclRight = c in a..b - 1 // a <= c && c < b

이렇게 하면 (b -1) 오른쪽 끝은 포함시키지 않는 범위를 만든다.

val range = 1..5 - 2

이렇게, 빼는 숫자를 늘릴수도 있다. 이러면 어찌되는가? 1..3 된다.

 

Not in 수도 있다.

 

val notWithin = 100 !in 10..99 // true

이렇게.

 

심지어 range integer에서만 있는게 아니라, character, String 에서도 있다.

 

println('b' in 'a'..'c') // true

println('k' in 'a'..'e') // false

 

println("hello" in "he".."hi") // true

println("abc" in "aab".."aac") // false

 

range 라는 놈은 어떤 object라고 생각하는게 편하다. 말이냐 하면,

Val range = a..b 

이런 식으로 range 객체를 변수에 할당할 수도 있다.

이렇게 하면

9 in range

이렇게 수도 있다.

 

 

 

Array ( 배열 )

특별한건 딱히 없다.

 

Kotlin provides many types for representing arrays: IntArray, LongArray, DoubleArray, FloatArray, CharArray, ShortArray, ByteArray, BooleanArray. Each array store elements of the corresponding type (Int, Long, Double and so on). Note, there is no StringArray.

 

다만 위와 같이,

타입별로 만들수 있는 Array 있다. String 제외한 기본 타입들은 전부 있다고 보면 된다.

 

 

val numbers = intArrayOf(1,2,3)

println(numbers.joinToString())

 

만약 Array 크기를 지정해서 만들고 싶다면?

 

val numbers = IntArray(5) 

이런식으로 하면 된다. 가장 문자가 대문자가 됐다.

요런 식으로 쓰면 된다.

 

println(array.first()) // "abc"

println(array.last()) // "efg"

println(array.lastIndex) // 2

 

위와 같은 편의 함수도 제공한다. ( String 과 유사해 보인다 )

 

값을 비교할 때는 == and !=  쓰면 안된다. (당연히)

 

만약 값이 같은지 비교하고 싶다면

 

println(numbers1.contentEquals(numbers2)) // true

println(numbers1.contentEquals(numbers3)) // false

 

 

 

Character ( 문자 )

 

코틀린은 문자를 숫자로 보진 않지만, 일관성은 어따 팔아먹었는지 ++ 같은건 또 지원한다. 이게 뭔 소리냐면.

 

val ch1 = ‘b’

ch1 + 1 // ‘c’

 

이게 된다는 말이다.

 

보통 문자는 ' ' 이렇게 따옴표 안에 한 글자 들어가는데...

 

val ch = '\u0040' // it represents '@'

println(ch) // @

 

요런 식으로 \u 를 붙여서 표현할 수도 있다. 헥사데시말 코드 로 나타내는 건데, 잘 모르겠다.

 

 

코틀린의 문자는 많은 멤버 함수를 갖고 있다.

 

 

  • isDigit() returns true if the given character represents a digit ('1''2', etc); otherwise, false;
  • isLetter() returns true if the given character represents a letter ('a''B''m', etc); otherwise, false;
  • isLetterOrDigit() returns true if the given character represents a letter or a digit; otherwise, false;
  • isWhitespace() returns true if the given character represents a whitespace (' ' or '\t'or '\n'); otherwise, false;
  • isUpperCase() returns true if the given character is an uppercase character; otherwise, false;
  • isLowerCase() returns true if the given character is a lowercase character; otherwise, false;
  • toUpperCase() returns the uppercase form of the given character;
  • toLowerCase() returns the lowercase form of the given character.

척봐도 쓸만해 보이는게 많다.

 

함수 (Function)

 

코틀린에서는 모든 함수는 "반환값" 을 갖는다. 정확히는 "반환 하는 것" 이 있다. void 같이, 아무것도 반환하지 않는다면??

 

val result = println("text")

println(result) // kotlin.Unit

This result is a special value called Unit, which means "no result".

 

항상 뭔가 뱉는다. 일단 아무것도 뱉는 같아도 kotlin.Unit 뱉음.

 

함수의 형식은 다음과 같다.

 

 

 

fun functionName(p1: Type1, p2: Type2, ...): ReturnType {

    // body

    return result

}

 

만약 한 줄로 표현할 수 있다면, curly braces 즉 중괄호는 생략 가능하다.

 

 

fun sayHello(): Unit = println("Hello")

 

Specifying the return type is optional, it can be inferred automatically:

fun sum(a: Int, b: Int) = a + b // Int

 

반환형은 안 써도 된다. (충분히 암시적이라면)

 

아, 함수의 argument 에 "기본 값" 을 설정할 수 있다.

 

default arguments allow programmers to create optional or special-case behaviour for functions.

 

fun findMax(n1: Int, n2: Int, absolute: Boolean = false): Int {

    val v1: Int

    val v2: Int

 

    if (absolute) {

        v1 = Math.abs(n1)

        v2 = Math.abs(n2)

    } else {

        v1 = n1

        v2 = n2

    }

 

    return if (v1 > v2) n1 else n2

}

 

 

 

 

val amount = calcEndDayAmount(ticketPrice = 10, soldTickets = 500, startAmount = 1000) 

 

이렇게 아규먼트 이름을 지정해서 수도 있고

 

이름 붙인거랑 붙인거랑 섞어서도 있는데

 

calcEndDayAmount(1000, ticketPrice = 10, soldTickets = 500) // 6000

 

섞어서 때는, 일단 이름으로 지정한 부터는 positional argument 없음.

 

 

 

Function 또한 일정한 타입으로 볼 수 있는데... ex) 3 -> Int , 3L -> Long

 

 

fun sum(a: Int, b: Int): Int = a + b

 

sum has a type of (Int, Int) -> Int.

 

fun sayHello() {

    println("Hello")

}

printHello has a type of () -> Unit 

 

이런식이다.

 

코틀린은 함수 또한 오브젝트처럼 다룰 수 있다.

 

Var mul = {a: Int, b:Int -> a + b}

이러면 mul 변수가 함수를 가리키는변수.

 

즉, 함수를 가리키는 "변수" 를 지정할 수 있다. 

 

mul 변수의 타입을 "명시적" 으로 표현해 볼까 그럼?

 

val mul: (Int, Int) -> Int = {a: Int, b: Int -> a + b}

 

요런식으로 표현이 된다.

 

그럼 함수를 mul 이라는 변수에 담았을 때는 저런 식으로, "참조" 할 수 있다 치고...

 

fun sum(a: Int, b:Int): Int = a + b

 

이렇게 이름이 부여된 함수를 넘기고 싶다면? 

 

fun sum(a: Int, b: Int): Int = a + b

val mul2 = { a: Int, b: Int -> a * b }

 

val increment = placeArgument(1, ::sum)

val triple = placeArgument(3, mul2)

 

:: 를 함수 이름 앞에 붙여서 넘기면 된다.

 

함수를 리턴하는 함수를 보면서, 함수의 타입에 대한 이해를 마치도록 하자.

 

 

 

fun getRealGrade(x: Double) = x

fun getGradeWithPenalty(x: Double) = x - 1

 

fun getScoringFunction(isCheater: Boolean): (Double) -> Double {

    if (isCheater) {

        return ::getGradeWithPenalty

    }

 

    return ::getRealGrade

}

 

 

이름이 없는 함수

 

이름이 없는 함수는 크게 두 가지로 나뉜다. 

 

하나는 익명, 하나는 람다.

 

  • fun(arguments): ReturnType { body } – this one is commonly called an "anonymous function".
  • { arguments -> body } – this one is commonly called a "lambda expression".

 

 

익명 함수가 이미 이름이 없는 함수인데 이게 뭔 개 소리인가 싶을 것이다. 그런데 어쩔 수없다. 영어 표현과 한국어 표현의 차이다.

영어로 표현하면 function which has no name. 요딴식이겠지.

 

하여간, 이름이 없는 함수는 => 어노니머스 함수 와 람다 함수. 이렇게 둘로 나뉜다.

(헷갈리니까 어노니머스 함수 라고 하자.)

 

fun(a: Int, b: Int): Int {

    return a * b

}

 

{ a: Int, b: Int -> a * b }

 

위의 두 표현은 "같은 표현" 이다.

 

 

val mul1 = fun(a: Int, b: Int): Int {

    return a * b

}

 

val mul2 = { a: Int, b: Int -> a * b }

 

이렇게 하면, 사실상 mul1 과 mul2 는 완전 똑같은 행위를 한다.

 

람다 함수의 경우, 매겨변수가 없을 때는

 

{ body }

 

요런 식으로 몸통만 써줘야 한다. -> 화살표는 반드시 생략한다.

 

람다를 매개변수로 받는 쪽은 여러 함축 단계를 거칠 수 있다. 매우 귀찮으니..

 

 

Lambdas and syntactic sugar

람다와, 시태틱 슈가

 

There are ways to make the code more readable for human without changing the code logic. If such a way is provided by the programming language and relates to syntax, the way is called syntactic sugar.

코드를 짜는 논리를 바꾸지 않으면서도, 코드를 좀 더 읽기 쉽게 하는 여러 방법이 있다. 만약 그런 것이 프로그래밍 언어 차원에서 지원될 때

우리는 이런걸 "syntactic sugar" 라고 부른다.

 

Kotlin promotes Functional Programming so there is syntactic sugar for it.

코틀린은 함수형 프로그래밍을 지향(?) 혹은 지원하며 이를 위한 문법적인 지원이 있다.

Let's recall the following example of passing the function as an argument:

 

fun isNotDot(c: Char): Boolean = c != '.'

val originalText = "I don't know... what to say..."

val textWithoutDots = originalText.filter(::isNotDot)


Rewrite it to pass a lambda:

 

val originalText = "I don't know... what to say..."

val textWithoutDots = originalText.filter({ c: Char -> c != '.' })

 

이런식으로 람다 함수를 쓰면, isNotDot 함수를 미리 선언할 필요가 없어진다.

 

Now we act! First of all, Kotlin infers types of many objects, and here specifying the c type isn't necessary:

originalText.filter({ c -> c != '.' })

 

그런데 솔직히 타입은 필요 없잖아? Char 를 굳이 써 줘야해? 생략하자.

 

Secondly, there are situations when the lambda is passed as the last argument. This is the case. Kotlin provides a way to eliminate these bracket sequences ({ }), allowing to write the lambda outside the parentheses:

originalText.filter() { c -> c != '.' }

 

그런데 함수의 매개변수로 넘어가는 람다가, "마지막 변수" 면, 사실 () 뒤쪽으로 빼내는게 좀더 보기 좋지 않냐? 그러니까 뺄 수 있어.

 

If the parentheses are left empty after this operation, they can be removed:

originalText.filter { c -> c != '.' }

 

아니 빼고 나니까 () 가 조금 거슬리네? 아무 변수도 없는데 저게 남아있잖아. 아 그럼, 아무것도 없는 괄호는 그냥 없앨 수 있게 하자.

 

() 안이 비어있으면 생략 가능함.

 

Finally, when there is a single parameter in a lambda, there is an opportunity to skip it. The parameter is available under the it name. The final version of the code removing dots is the following:

val originalText = "I don't know... what to say..."

val textWithoutDots = originalText.filter { it != '.' }

 

음... 다 좋은데. 저 c 가 마음에 안들어. 아니, 어차피 매개변수가 딱 하나인데... 굳이 c 요런 식으로 하나를 써 줘야해? 매개변수가 하나밖에 없을 때는 그냥 c 같은 건 생략할 수 있게 하는 건 어떨까?

 

람다는, 매개변수 없으면 -> 없어지잖아. 그 하나뿐인 매개변수는 it 으로 표현할 수 있게 하자..

 

 

 

결론:

람다를 바꿔 볼까? 코틀린은 형변환 해주니까, 람다 안의 매개변수 타입을 굳이 지정해 필요 없음.  그러니까 :Char 빼보자.

 

그런데 만약 람다가 마지막 아규먼트로 쓰인다? 그럼 () 오른쪽으로 있음.

그런데 왼쪽에 남은 () 안에 아무런 매개변수도 없잖아? 이걸 생략할 있어.

 

하지만 람다가 c -> c 이렇게 쓰는것도 별로인 같아.

왜냐면 매개변수가 하나밖에 없는데 굳이 -> 써야 할까?

 

이럴 때는 -> 빼고 it 있도록 했어. 짱이지?

 

 

.... 아주 그냥;;

 

Lambda 마지막 줄은 자동 return 이야. 마지막 줄에서 뱉는 값이 자동으로 return 된다는 말이지.

 

그런데 중간에 값을 뱉고 싶을수도 있잖아?

 

val textWithoutSmallDigits = originalText.filter {

    if (!it.isDigit()) {

        return@filter true

    }

        

    it.toString().toInt() >= 5

}

 

 

이런 식으로 있어

중간에 return 넣고, @ 마킹해줘야해

보통 @ 에다가 붙이는 이름은, 자기를 콜하는 함수 이름이야. (자기를 매개변수로 받는 )

 

lambda 물론 closure 개념을 갖고 있어.

 

클로저는 나중에 따로 정리하도록 하자.

 

일단 간단히..

 

클로저란?

MDN에서는 클로저를 다음과 같이 정의하고 있다.

클로저는 독립적인 (자유) 변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을기억한다’.

흔히 함수 내에서 함수를 정의하고 사용하면 클로저라고 한다. 하지만 대개는 정의한 함수를 리턴하고 사용은 바깥에서 하게된다. 말로 설명하면 설명하기가 복잡하니 우선 코드를 보자.

 

 

function getClosure() {

  var text = 'variable 1';

  return function() {

    return text;

  };

}

 

var closure = getClosure();

console.log(closure()); //

 

 

자유 변수를 가리키는 함수. 클로저 안에 정의된 함수는 만들어진 환경 기억해.

여기서 getClousre 함수가 아니라, 안에서 뱉어지는 function() {return text; }

클로저야. 이 클로저는 말 그대로 "문을 닫는 느낌" 이지. text 배열을 자기 방 안에 넣고, 문을 닫아버리는 거야.

 

--

 

if 와 when

 

코틀린에서는

 

if 문이 , 아니라 표현이다.

 

표현은 반드시 반환값을 갖는 것이며, 문은 반환 값을 갖는다기 보다, 값들에 변화를 일으키는 

것들을 일컫는다.

 

만약 if 표현으로서 값을 반환하는 용도로 쓰인다면 반드시 else 있어야 한다.

 

 

또 switch 대신 when 이 있다.

 

When 또한 Expression 으로 있는데

때는 if 처럼 반드시 else 있어야 .

 

val result = when (op) {

    "+" -> a + b

    "-" -> a - b

    "*" -> a * b

    else -> "Unknown operator"

}

println(result)

 

보면, when(op)  이런 식으로 ( ) 안에 변수가 하나 들어가고

 

op 해당하는 값이 있으면 -> 분기를 타는 방식이다.

 

when (n) {

    0 -> println("n is zero")

    in 1..10 -> println("n is between 1 and 10 (inclusive)")

    in 25..30 -> println("n is between 25 and 30 (inclusive)")

    else -> println("n is outside a range")

}

 

 

만약 when 뒤에 아무런 변수도 없다? 그냥 모두 boolean 조건이 필요함.

 

    when {

        n == 0 -> println("n is zero")

        n in 100..200 -> println("n is between 100 and 200")

        n > 300 -> println("n is greater than 300")

        n < 0 -> println("n is negative")

        // else-branch is optional here

    }

이런 식으로...

 

 

Switch 다르게, 번에 분기만 있다. (여러 expression 실행 불가)

 

when(str) {

   “Hello”,”world” -> {

 

   }

}

이런 식으로는 쓸 수 있다. 이렇게 한 분기.

 

반복문

 

repeat(n) 이라는 함수가 있다. Argument int 값을 받아 n 실행한다.

 

While 문은 여타 while 문과 같음.

 

 scanner.hasNext() for strings and scanner.hasNextInt()

 

scanner hasNext()  를 활용할 수 있다.

 

라벨 개념

 

 

loop@ for (i in 1..3) { 

    for (j in 1..3) {

        println("i = $i, j = $j")   

        if (j == 3) break@loop  

    }  

}  

 

귀찮으니 간단히 정리하고 넘어가겠다.

 

val fileName = "src/reading.txt"

val lines = File(fileName).readText()

print(lines)

이렇게 가져오면, 파일의 모든 값을 한번에 가져옴.

 

메모리 제한은 2GB 정도다.

 

val fileName = "src/reading.txt"

val lines = File(fileName).readLines() 

for (line in lines){

    println(line)

} 

 

readLine 도 2GB. 사실상 전부 불러오고, List<String> 안에 한 줄씩 넣어놨을 뿐임.

 

readBytes() – may be helpful if you need to store file contents as an array of bytes:

이 것도, 결국 readText() 를 구현하기 위해 사용하는 세부 함수라서 2GB 메모리 제한임.

 

그 이상의 파일은 다음과 같은 방법을 사용해보라.

 

File(fileName).forEachLine { print(it) }

 

일단 넘어가자. 파일은 나중에 쓸 일이 있을 때 한번 더 훑어봐도 늦지 않는다.

 

 

 

클래스

 

Properties 반드시 초기화 돼야 .

 

Also, a property has a strict type. The type can be any. It can be a standard type like a number or a string or a custom type. So a property type can be your own class and even the same class where the property is declared.

 

 

 

 

Constructors are class members that initialize a new object of the class.

 

class Size {

    var width: Int = 0

    var height: Int = 0

 

    constructor(_width: Int, _height: Int) {

        width = _width

        height = _height

    }

}

 

이런 식의 생성자를 “Secondary constructor” 라고 한다. 차순위 생성자?

대체 생성자?

 

 

Omitting default values

Classes in Koltin have useful feature: you don't have to declare a default property value if the value is assigned in the constructor of the class:

 

 

 

 

Primary Constructor

생성자

존나 코틀린의 새로운 문법 하나인데

 

Class Size constructor(width: Int, height: Int) {}

 

요런 식으로 constructor 선언하는거임.

 

class Size constructor(width: Int, height: Int) {

    val width: Int = width

    val height: Int = height

    val area: Int = width * height

}

 

Primary constructor constructor 키워드는 생략이 되며,

심지어 primary constructor 에서는 변수 선언도 가능하다.

 

class Size(val width: Int, height: Int) {

    val height: Int = height

    val area: Int = width * height

}

 

위와 같이. Width 없던 변수를 선언 놓는 것이다. 

 

위의 클래스 선언은 사실상 번째 것과 다를게 없다.

 

 

class Size(val width: Int, val height: Int)

 

이런 식으로,  {} 생략하고 아주 간단한 Single line class 만들 수도 있다.

 

  • Any Kotlin class has a constructor to initialize its objects.
  • A constructor is available for invocation under the class name.
  • There are different ways to declare a constructor but a primary constructor is the most concise.
  • If a class has no custom constructors, then there is an implicit default constructor with no arguments and it does nothing.

 

 

모든 코틀린 클래스는 오브젝트를 초기화 하기위한 생성자를 갖고 있다.

컨스트럭트는 클래스 이름 아래에 있을 있다.

커스텀 컨스트럭터가 없다면, 기본 생성자가 암시적으로 존재한다.

 

 

enum class Rainbow(val color: String) {

    RED("Red"),

    ORANGE("Orange"),

    YELLOW("Yellow"),

    GREEN("Green"),

    BLUE("Blue"),

    INDIGO("Indigo"),

    VIOLET("Violet")

}

 

Enum 

 

enum class Rainbow(val color: String, val rgb: String) {

    RED("Red", "#FF0000"),

    ORANGE("Orange", "#FF7F00"),

    YELLOW("Yellow", "#FFFF00"),

    GREEN("Green", "#00FF00"),

    BLUE("Blue", "#0000FF"),

    INDIGO("Indigo", "#4B0082"),

    VIOLET("Violet", "#8B00FF");

 

    fun printFullInfo() {

        println("Color - $color, rgb - $rgb")

    }

}

 

 

Enum 기본 속성들

 

Name -> 그냥 이름 Rainbow.RED -> RED

Ordinal -> enum 순서?  Rainbow.GREEN.ordinal -> 3

Values ()-> enum instances 들을 반환.

 

fun isRainbow(color: String) : Boolean {

    for (enum in Rainbow.values()) {

        if (color.toUpperCase() == enum.name) return true

    }

    return false

}

 

반복문은 위와 같이 enum .

 

 valueOf() - returns an instance of Enum by its name with String type and case sensitivity

 

 

If you want to extend your Enum but with static context, then you need to wrap your method with companion object keywords, let's modify our Rainbow in order to find an instance by RGB parameter:

 

Companion object 여기서 처음 등장한다.

 

Static 으로 class member 추가할 사용한다.

 

enum class Rainbow(val color: String, val rgb: String) {

    RED("Red", "#FF0000"),

    ORANGE("Orange", "#FF7F00"),

    YELLOW("Yellow", "#FFFF00"),

    GREEN("Green", "#00FF00"),

    BLUE("Blue", "#0000FF"),

    INDIGO("Indigo", "#4B0082"),

    VIOLET("Violet", "#8B00FF"),

    NULL("", "");

 

    companion object {

        fun findByRgb(rgb: String): Rainbow {

            for (enum in Rainbow.values()) {

                if (rgb == enum.rgb) return enum

            }

            return NULL

        }

    }

 

    fun printFullInfo() {

        println("Color - $color, rgb - $rgb")

    }

}

 

이렇게 쓴다.

 

Member function 

 

자바에서는 클래스 안에 있는 함수를메서드라고 표현하곤 하는데,

여기서는 Member function 이라고 쓴다.

 

코틀린은 클래스에 function 확장 선언하는 기능을 제공한다.

 

이를 우린확장 함수” Extension Function 이라고 부른다.

 

fun String.repeated(): String = this + this

 

Fun 붙이고, “클래스”.”함수이름 하고내용을 선언하면 .

 

"ha".repeated() => “haha”

 

 

  • The syntax to define an extension function is like defining a top-level function. Just write the name of a class which you would like to extend and a dot before the function name. The class to be extended is called a receiver type.

, “클래스리시버 타입이라고 칭한다.

This 키워드를 사용하면, 확장 함수를 사용하는인스턴스 참조할 있다.

 

So the issue is solved: Kotlin developers are able to add any functions to any classes they want.

 

확장함수는 나름 안정성을 확보하기 위한 규칙을 갖고 있다.

일단, 확장함수 끼리는 중복이 안되며 (컴파일이 실패함), 이미 멤버 함수가 있을 , 확장함수가 이를 오버라이딩 없다.

 

 

fun product.takeFunction(productName: String): Product {

this.~~~()

this.~~~()

  return ~~~

}

 

 

 

 

블로그 이미지

맛간망고소바

,