devlog of ShinJe Kim

[TIL] 2019-08-29 (목)

|

Today I Learned

  • iterator 패턴: 이터레이터 패턴은 컬렉션 구현 방법을 노출시키지 않으면서 그 집합체 안에 들어있는 모든 항목에 접근할 수 있는 방법을 제공함. 컬렉션 객채 안에 들어있는 모든 항목에 접근하는 방식이 통일되어있으면 어떤 종류의 집합체에 대해서도 사용할 수 있는 다형적인 코드를 만들 수 있음. 이터레이터 패턴을 사용하면 모든 항목에 일일이 접근하는 작업을 컬렉션 객체가 아니라 반복자 객체에서 하게 됨. 이렇게 하면 집합체의 인터페이스 및 구현이 간단해질 뿐만 아니라 집합체에서는 반복작업에서 손을 떼고 원래 자신이 할 일(객체 컬렉션 관리)에만 전념할 수 있음. 즉 접근 기능과 자료 구조를 분리시켜 객체화 한 것.
  • 디자인 원칙 중 바뀌는 부분을 캡슐화하라는 원칙이 있음. iterator도 어떻게 보면 반복을 캡슐화한 것.
  • 또 하나의 디자인 원칙은 클래스를 바꾸는 이유는 한 가지 뿐이어야 한다는 것.
  • 언더스코어(_): 사용하지 않는 인자를 표기할 떄 씀(프로덕션 레벨로 컴파일하면 사용하지 않는 객체?가 있을 때 에러가 발생함. 과거에는 이를 해결하기 위해 의미 없는 코드를 작성해서 모든 객체를 사용했음. 하지만 최근의 언어들은 언더스코어로 이를 표현할 수 있게 해준다)
  • 함수형 프로그래밍(functional programming): side-causes(함수 안에 숨겨진 input)와 side-effects(함수 안에 숨겨진 output)가 최대한 없도록 선언하는 프로그래밍 패러다임.
  • 1급 객체: 3가지 조건을 만족해야 함. 변수나 데이터에 할당할 수 있어야 함/객체의 인자로 넘길 수 있어야 함/객체의 리턴값으로 리턴할 수 있어야 함. 코틀린의 함수는 1급 객체이지만 자바의 함수는 1급 객체가 아님. 함수형 프로그래밍의 함수는 1급 객체임.
  • 모달과 모달리스 : 모달은 해당 창 이외의 부분에 포커스가 가지 않도록 블록해놓은 것. 모달리스는 해당 윈도우가 열려있어도 다른 부분에 포커스가 가능.
  • 새로운 개념을 공부할 떄 그 개념의 관련 개념들을 모두 정리하여 공부하는 것이 중요함.

Todo

  • Observer 디자인 패턴이란?
  • async의 단점이 뭐다? -> observer디자인 패턴 -> rx

[Kotlin] 내부 클래스(inner class)와 중첩 클래스(nested class)

|

내부(inner) 클래스와 중첩(nested) 클래스

코틀린 공식문서코틀린 인 액션 책을 참고하여 작성하였습니다.

자바에서는 A 클래스 안에 B 클래스를 정의하면 B 클래스는 자동으로 내부 클래스가 되었습니다. 하지만 코틀린에서는 반대입니다. 한 클래스안에 다른 클래스를 정의하면 기본적으로는 중첩 클래스가 되고, 내부 클래스로 만들고 싶다면 inner 키워드로 클래스를 선언해야 합니다. 내부 클래스는 기본적으로 외부 클래스를 참조하게되지만 중첩 클래스는 그렇지 않습니다. 아래의 예시를 보겠습니다.

// nested class 중첩 클래스
class Outer {
    private val bar: Int = 1
    class Nested {
        fun foo() = 2
    }
}

val demo = Outer.Nested().foo() // == 2

위와 같이 중첩 클래스에서는 외부 클래스를 참조하지 않기 때문에 Outer.Nested().foo()의 값이 2가 됩니다.

// inner class 내부 클래스
class Outer {
    private val bar: Int = 1
    inner class Inner {
        fun foo() = bar
    }
}

val demo = Outer().Inner().foo() // == 1

반면, 내부 클래스에서는 외부 클래스를 항상 참조하고 있기 때문에 Outer().Inner().foo()의 값이 1이 됩니다. 객체를 항상 참조하고 있다는 것은 어떤 의미를 가질까요? (자바에서) 객체가 삭제되는 시점은 객체가 더 이상 사용되지 않을 때입니다. 그런데 내부 클래스를 사용하면 항상 외부 클래스의 객체를 참조하기때문에 객체가 적절한 시점에 삭제되지 못하고 메모리 누수가 발생합니다.

이러한 누수가 위험한 이유는 명시적(컴파일 시 오류가 발생하는 등 명식적으로 알 수 있는 것)인 것이 아니라 암묵적(프로그래머가 발견하기 전까지는 알 수 없는 것)인 것이기 때문입니다. 따라서 특별한 경우가 아니라면 내부 클래스 사용을 지양하고 중첩 클래스를 사용하는 것이 권장된다고 합니다.

기존의 자바와 다르게 inner 키워드를 선언해야만 내부 클래스를 사용할 수 있도록 설계한 이유와도 관련이 있을것 같다는 생각이 드는데.. 아시는 분은 알려주시면 감사하겠습니다.

참고 문헌

[TIL] 2019-08-28 (수)

|

Today I Learned

  • 리눅스/유닉스: 모든 것은 파일이다. 다형성(polymorphism)(파일로 모든것을 처리). 장점은 일관된 양식. 단점은 각 장치?모듈? 들의 특성이 다름.
  • 윈도우: 모든 것이 다름. 각자의 api가 있음. 모두 형식이 다르다는 것이 단점이기도 하지만 각 모듈들의 특성에 맞게 변환해준다는 것이 장점임.
  • 직렬화(serializtion): 원래는 객체를 바이트스트림 형태로 변환해서 저장하는 것을 의미함(반대는 역직렬화/unserialization) 그런데 요즘에는 꼭 바이트스트림이 아니더라도 JSON이나 xml등의 다양한 형태로 객체를 변환하는 것을 말함. marshalling/unmarshalling, coding/decoding 이라고도 함.
  • 순수 함수: (함수형 프로그래밍) 동일한 인자를 넣었을 때 동일한 결과가 나오는 것을 의미함.
  • 내부클래스는 함부로 쓰면 안된다. 내부 클래스는 항상 바깥 클래스를 참조하기떄문에 누수의 영역이 될 수 있다. 내부 클래스를 써야 할 때는 중첩 클래스를 써야한다. 중첩 클래스는 바깥쪽 클래스를 참조하지 않는다.
  • TODO: 캐시/쿠키/세션 정리해서 글 쓰기

[Kotlin] 인터페이스

|

인터페이스(Interfaces)

이 글은 코틀린 공식문서를 공부하며 번역한 글입니다. 틀린 부분이나 어색한 부분을 댓글로 알려주시면 감사하겠습니다.

코틀린 인터페이스에서는 추상메소드와 구현이 있는 메소드 모두 선언할 수 있습니다. 인터페이스와 추상클래스와 다른 점은 인터페이스는 상태(필드)를 저장할 수 없다는 것입니다. 인터페이스는 추상적이거나 accessor implementations를 제공하는 프로퍼티만을 가질 수 있습니다.

interface MyInterface{
    fun bar()
    fun foo(){
        // optional body
    }
}

인터페이스 구현하기(Implementing Interfaces)

인터페이스를 사용하는 방법은 자바보다 간편합니다. 자바에서는 [class] implements [interface]의 형식으로 구현했지만 코틀린에서는 아래와 같이 클래스 이름 뒤에 콜론(:)을 붙인 뒤 인터페이스 이름을 적으면 됩니다.

class Child : MyInterface{
    override fun foo(){
        // body
    }
}

인터페이스의 프로퍼티(Properties in Interfaces)

인터페이스에서 프로퍼티를 사용할 수 있습니다. 인터페이스에서 선언된 프로퍼티는 추상 프로퍼티이거나 접근자로 구현할 수 있어야 합니다. 인터페이스에 선언된 프로퍼티는 backing fields를 가질 수 없으므로 접근자가 참조할 수 없습니다.

interface MyInterface{
    val prop: Int // abstract

    val propertyWithImplementation: String
        get() = "foo"
    
    fun foo() {
        print(prop)
    }
}

class Child : MyInterface{
    // 코틀린에서는 override 변경자를 꼭 사용해야합니다. 
    override val prop: Int = 29 
}

인터페이스 상속(Interface Inheritance)

인터페이스는 다른 인터페이스에서 상속받을 수 있으며, 상속 받은 인터페이스에서 멤버 구현과 새로운 함수 및 프로퍼티 선언을 할 수 있습니다. 당연히 인터페이스를 구현한 클래스에서는 구현되지 않은 부분만 구현하면 됩니다.

interface Named {
    val name: String
}

interface Person : Named {
    val firstName: String
    val lastName: String

    override val name: String get() = "$firstName $lastName"
}

data class Employee(
    // implementing 'name' is not required
    override val firstName: String,
    override val lastName: String,
    val position: Position
) : Person

오버라이딩 충돌 해결하기(Resolving overriding conflicts)

상위 타입(supertype)을 많이 선언하면 같은 메소드를 한 번 이상 상속 받는 것처럼 보입니다.

interface A {
    fun foo() { print("A")}
    fun bar()
}

interface B {
    fun foo() { print("B")}
    fun bar() { print("bar")}
}

class C : A {
    override fun bar() { print("bar")}
}

class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }

    override fun bar() {
        super<B>.bar()
    }
}

인터페이스 A와 B 모두 foo()와 bar() 메소드를 선언하고 있습니다. foo() 함수는 A와 B 인터페이스 모두 구현하고 있지만 bar() 함수는 B 인터페이스에서만 구현하고 있습니다( bar() 함수가 인터페이스 A에서 추상메소드로 선언되어 있지 않은 이유는, 함수의 바디가 없다면 이것이 인터페이스의 기본 형태이기 때문입니다). 만약 C 클래스에서 A 인터페이스를 구현한다면, bar() 메소드를 오버라이드하여 구현해야만 합니다.

하지만 만약 D 클래스에서 A와 B 인터페이스를 구현한다면 A와 B 인터페이스에 있는 모든 메소드를 구현해야만 합니다. 또한 D 클래스가 함수들을 어떻게 구현할 것인지 구체적으로 작성해야 합니다. 이 규칙은 단일 구현(single implementation)을 한 bar() 메소드와 다중 구현(multiple implementations)을 한 foo() 메소드 모두에 적용됩니다.

참고 문헌

[TIL] 2019-08-26 (월)

|

Today I Learned

Kotlin in Action 3장

3.3.4 확장 함수는 오버라이드 할 수 없다

코틀린은 확장 함수를 정적으로 결정하기 떄문에 확장 함수를 오버라이드 할 수 없다. 만약 어떤 클래스의 확장 함수와, 해당 클래스의 멤버 함수의 이름이 같다면 확장 함수가 아니라 멤버 함수가 호출된다.

3.4.2 가변 인자 함수: 인자의 개수가 달라질 수 있는 함수 정의 코틀린에서는 아래와 같이 파라미터 앞에 vararg 변경자를 붙이면 가변 인자를 사용할 수 있다(자바에서는 타입 뒤에 ...를 붙였다)

fun listOf<T> (vararg values: T) List<T>{ ... }

3.4.3 값의 쌍 다루기: 중위 호출과 구조 분해 선언 코틀린에는 중위 호출(infix call)이라는 특별한 호출 방식이 있다. 중위 호출 시에는 수신 객체와 유일한 메소드 인자 사이에 메소드 이름을 넣는다(이 떄 객체, 메소드 이름, 유일한 인자 사이에는 공백이 들어가야 한다). 중위 호출은 인자가 하나인 일반 메소드나, 인자가 하나인 확장 함수에 사용할 수 있다.

1.to("one") // "to" 메소드를 일반적인 방식으로 호출함
1 to "one" // "to" 메소드를 중위 호출 방식으로 호출함

함수를 중위호출에 사용하게 허용하고 싶으면 infix 변경자를 함수 선언 앞에 추가해야 한다. 구조 분해 선언(destructuring declaration) 부분이 이해가 안됨. 다시 정리해보고 여쭤보기.

3.5 문자열과 정규식 다루기

3.5.1 문자열 나누기 코틀린에서는 아래와 같이 하나 이상의 문자를 인자로 받는 split 함수를 사용할 수 있다.

>>>println("12.345-6.A".split(".", "-"))
(12, 345, 6, A)