프로그래밍 방식
1. 명령형 프로그래밍(Imperative Programming, IP)
- 프로그래밍의 상태와 상태를 변경하는 구문의 관점에서 연산을 설명하는 방식
- 무엇(What)을 ‘어떻게(How)’ 할 것인지에 집중한다.
- 1-1. 절차지향 프로그래밍(Procedural Programming, PP) : 문제를 순차적으로 처리하여 프로그램을 만드는 방식 (C, C++)
- 절차지향 프로그래밍은 함수를 구조화할 뿐 데이터 자체를 구조화하지 못하기 때문에, 소프트웨어의 규모가 커지거나 변화가 생기면 각각의 함수에 의존하는 부분을 매번 고쳐야 하는 등 작업이 복잡해져 스파게티 코드를 유지보수하는 데 어려움을 겪는다.
- 1-2. 객체지향 프로그래밍(Object-Oriented Programming, OOP) : 객체들 간의 상호작용을 통해 프로그램을 만드는 방식 (C++, Java, C#)
2. 선언형 프로그래밍Declarative Programming, DP)
- 프로그램이 무엇과 같은 지를 설명하는 방식
- ‘무엇(What)을’ 할 것인지에 집중한다.
- 2-1. 함수형 프로그래밍(Functional Programming, FP) : 수학적 함수의 계산을 통해 자료를 처리하고 함수를 조합하여 프로그램을 만드는 방식 (Kotlin)
함수형 프로그래밍
- 모든 것을 순수 함수로 나누어 문제를 해결하는 기법
- 함수 단위의 코드 재사용이 매우 용이하고, 불변성을 지향하기 때문에 프로그램의 동작을 예측하기가 쉬워진다.
- 다중 스레드를 사용해도 안전하다.
- 다중 스레드 프로그램에서는 적절한 동기화 없이 같은 데이터를 여러 스레드가 변경하는 경우 가장 많은 문제가 생긴다.
- 불변 데이터 구조를 사용하고 순수 함수를 그 데이터 구조에 적용한다면 다중 스레드 환경에서 같은 데이터를 여러 스레드가 변경할 수 없다.
- 따라서 복잡한 동기화를 적용하지 않아도 된다.
순수 함수(Pure function)
- 함수 : 동일한 입력에 대해 항상 동일한 출력을 반환하는 것
- 함수 내부에서 인자의 값을 변경하거나 프로그램 상태를 변경하는 *부수 효과가 없는 함수
- 부수 효과(Side Effect)가 없는 : 다른 객체의 상태를 변경하지 않고, 함수 외부나 다른 바깥 환경과 상호작용하지 않는 것
- 부수 효과가 있는 : 값을 반환하는 메서드나 함수가 외부 상태를 변경하는 것
- C 언어와 달리 Kotlin 에서는 포인터 주소를 사용하지 않기 때문에, 주소 참조(Call By Reference)가 아닌 값을 복사하여 전달하는 값에 의한 호출(Call By Value)이 일반적으로 발생한다.
- 테스트하기 쉽다 : 부수 효과가 있는 함수는 그 함수를 실행할 때 필요한 전체 환경을 구성하는 준비 코드가 따로 필요하지만, 순수 함수는 그런 준비 코드 없이 독립적으로 테스트할 수 있다.
불변성 (Immutability)
- 값이나 상태를 변경할 수 없는 것
- 함수형 프로그래밍에서는 일단 만들어지고 나면 내부 상태가 절대로 바뀌지 않는 불변 객체를 사용해 프로그램을 작성한다.
- 불변 객체는 생성 시점 이후 한 번 정의된 상태를 유지하며 변경되지 않기 때문에, 스레드 간 안전성이 보장되어 동기화 문제를 해결할 수 있다.
- 데이터의 상태를 변경하는 대신, 변경된 새로운 데이터를 생성하여 상태 변경으로 인한 버그 발생 가능성을 줄일 수 있습니다.
[ 가변성(Mutability) ]
- 상태를 가지는 경우
- 메모리의 저장된 값을 변경하는 행위
- 메모리에 저장된 하나의 값을 누구나 변경할 수 있다는 것은 무분별한 상태 변경으로 이어질 수 있다.
[ 가변성을 제한하는 방법 ]
- 읽기 전용 프로퍼티 val
- 읽기 전용 컬렉션
- data class 의 copy() 함수
일급 객체(First-class citizens)
- 함수형 언어에서는 모든 것이 객체가 된다.
- 자바에서는 클래스만이 객체가 되고 함수는 클래스 안의 메서드로 구현되지만, 함수형 언어인 코틀린에서는 함수 또한 객체로 관리할 수 있다.
- 일급 객체 조건
- 함수(프로그램의 행동을 나타내는 코드 조각)를 일반 값처럼 다룰 수 있다. 함수를 변수에 저장할 수 있다.
- 함수를 인자로 다른 함수에 전달할 수 있다.
- 다른 함수의 결과 값으로 반환될 수 있다.
- 간결성 : 함수형 코드는 그에 상응하는 명령형 코드에 비해 더 간결하며 우아하다. (순수) 함수를 값처럼 활용할 수 있으면 더 강력한 추상화를 할 수 있고 강력한 추상화를 사용해 코드 중복을 막을 수 있다.
고차 함수(Higher-order function)
- 다른 함수를 인자로 받거나, 함수를 반환하는 함수
- 람다나 함수 참조를 인자로 넘길 수 있거나, 람다나 함수 참조를 반환하는 함수
익명 클래스(Anonymous Class)
- 단 한번만 객체로 생성하고 더 이상 재사용되지 않는 클래스
- 클래스 선언과 동시에 객체를 생성하는 일회용 클래스
- 만일 어느 메서드에서 부모 클래스의 자원을 상속받아 재정의하여 사용할 자식 클래스가 한 번만 사용되고 버려질 자료형이면, 굳이 상단에 클래스를 정의하기보다는, 지역 변수처럼 익명 클래스로 정의하고 스택이 끝나면 삭제되도록 하는 것이 유지보수면에서나 프로그램 메모리면에서나 이점을 얻을 수 있다.
- UI 이벤트 처리, 스레드 객체 등 단발성 이벤트 처리에 자주 애용된다.
- 전혀 새로운 클래스를 익명으로 사용하는 것이 아니라, 이미 정의되어 있는 클래스의 멤버들을 재정의 하여 사용할 필요가 있을 때 그리고 그것이 일회성으로 이용될 때 사용하는 기법이다.
- 익명 클래스 방식으로 선언한다면 오버라이딩 한 메소드 사용만 가능하고, 새로 정의된 메서드는 외부에서 사용이 불가능하다.
- 익명 클래스도 내부 클래스의 일종이기 때문에, 외부의 지역 변수를 이용하려고 할 때 똑같이 내부 클래스의 제약을 받게 된다. 따라서 내부 클래스에서 가져올 수 있는 외부 변수는 final 상수인 것만 가져와 사용할 수 있다.
- 익명 클래스 선언 방법
- 부모를 상속받은 자식 익명 클래스 선언
- 인터페이스를 구현한 익명 클래스 선언(익명 구현 객체)
- 인터페이스의 가장 큰 본질은 다중 상속(구현)이 가능하다는 것인데, 둘 이상의 인터페이스를 갖거나, 하나의 클래스를 상속받고 동시에 인터페이스를 구현하는 형태로는 익명 구현 객체로 불가능하다.
- 일회용 용도일지라도 다중 구현한 클래스는 따로 정의하여 사용해야 한다.
- 그러나 익명 내부 클래스를 사용하여 코드를 함수에 넘기거나 변수에 저장할 수 있기는 하지만 상당히 번거롭다.
람다(람다식, 람다 표현식)
- 다른 함수에 넘길 수 있는 작은 코드 조각
- 익명 함수를 간단히 표현하는 함수로서, 값처럼 여러 곳에 전달할 수 있는 동작의 모음이다.
- 인터페이스로만 만들 수 있다.
- 자바 8 이전에는 메서드에 인자로 익명 클래스의 인스턴스를 만들어야 했다. 코틀린에서는 익명 클래스 인스턴스 대신 람다를 넘길 수 있다.
- 이런 코드가 작동하는 이유는 익명 클래스에 추상 메소드가 단 하나만 존재하기 때문이다. (함수형 인터페이스(SAM(Single Abstract Method) 인터페이스)
- 외부 변수를 캡처하지 않는 경우(외부 상태에 의존하지 않는 경우), 람다 인스턴스는 한 번만 생성되고 재사용할 수 있다.
- 람다 표현식 내에서 외부 변수를 사용하지 않고, 오직 람다 자체 내에서 정의된 로컬 변수나 상수만 사용하는 경우
- 그러나 객체를 명시적으로 선언하는 경우(익명 클래스를 생성하는 경우), 메서드를 호출할 때마다 새로운 객체가 생성된다.
- 그러나 람다가 외부 변수를 캡처하는 경우, 람다가 생성되는 시점마다 새로운 익명 클래스 객체가 생성된다.
- 이런 경우에는 실행 시점에 익명 클래스가 생성되면서 추가 비용이 발생하므로, 일반 함수를 사용한 구현보다 덜 효율적이다.
인라인 함수(Inline Function)
- 람다가 외부 변수를 캡처하는 경우, inline 키워드를 사용한다.
- inline 변경자를 함수에 붙이면, 컴파일러는 그 함수를 호출하는 모든 문장을 함수 본문에 해당하는 바이트코드로 바꿔치기해준다.
- 객체가 항상 새로 생성되는 것이 아니라 해당 함수의 내용을 호출한 함수에 넣는 방식으로 컴파일 코드를 작성한다.
- 부가 비용을 상당히 줄일 수 있다. 함수 호출 비용을 감소시킬 뿐만 아니라, 람다를 표현하는 클래스와 해당 람다 인스턴스에 필요한 객체를 생성하지 않아도 된다.
- 인라이닝하는 함수가 큰 경우, 함수의 본문에 해당하는 바이트코드를 모든 호출 지점에 복사해 넣으면 전체 바이트코드의 크기가 상당히 커질 수 있다.
'안드로이드 > Kotlin' 카테고리의 다른 글
[Kotlin] 추상 클래스(abstract class), 인터페이스(interface) (0) | 2024.12.08 |
---|---|
[Kotlin] 데이터 클래스(Data Class) (0) | 2024.12.06 |
[Java] 읽기 전용 컬렉션(List, Set, Map), ArrayList, LinkedList, Array (0) | 2024.12.05 |