❓❓❓
Decorator 패턴
기존 객체를 상속하지 않고, 합성을 통해 기능을 확장하는 패턴. 즉, 기능을 추가한 객체가 기존 객체를 멤버로 포함하는 방식. 기존 코드를 수정하지 않고도 새로운 기능을 동적으로 추가할 수 있지만, 객체가 중첩되는 형태로 구성되므로 구조가 복잡
//IceCream 클래스에 토핑 추가를 만들고 싶을 때 확장을 통해 쉽게 기능 추가 가능
//지금은 구조가 단순하지만 복잡해지거나 수정이 불가한 경우 유용하게 사용 가능
class IceCream{
fun order(){
println("Order Ice Cream")
}
}
fun IceCream.addCookieTopping()=println("Topping - cookie")
fun main(){
val iceCream= IceCream()
iceCream.order()//Order Ice Cream
iceCream.addCookieTopping()//Topping - Cookie
}
//프로퍼티의 경우 getter 사용 필수. getter를 통해 함수처럼 값 반환 가능
//val IceCream.cookieTopping=""// 초기화 불가
val IceCream.cookieTopping:String
get()="Topping - cookie"
fun main(){
val iceCream= IceCream()
iceCream.order() //Order Ice Cream
println(iceCream.cookieTopping)//Topping - Cookie
}
//확장 프로퍼티로 계산된 정보 반환 가능
class IceCream(val size:Int){
fun order(){
println("Order Ice Cream")
}
}
val IceCream.sizeLabel:String
get()=when{
size<=200 -> "small"
size<500 -> "medium"
else -> "large"
}
fun main(){
val iceCream= IceCream(200)
println(iceCream.sizeLabel)//small
}
//String 확장 함수
fun String.addBang()=println("$this!")
public static final void addBang(@NotNull String $this$addBang) {
//null-safety 보장을 위해 null 체크 삽입. 컴파일러가 자동으로 추가.
Intrinsics.checkNotNullParameter($this$addBang, "$this$addBang");
//호출할 때의 this가 $this$addBang으로 변환되어 확장 함수 내용 처리
String var1 = $this$addBang + '!';
System.out.println(var1);
}
//IceCream 클래스의 확장 함수와 프로퍼티
class IceCream
fun IceCream.addCookieTopping()=println("Topping - cookie")
val IceCream.cookieTopping:String
get()="Topping - cookie"
//위 예제와 달리 호출객체(this)를 사용하지 않았기에
//단순히 확장 함수 값을 그대로 사용(해당 값 출력)
public static final void addCookieTopping(@NotNull IceCream $this$addCookieTopping){
Intrinsics.checkNotNullParameter($this$addCookieTopping,"$this$addCookieTopping");
String var1 = "Topping - cookie";
System.out.println(var1);
}
//확장 프로퍼티 getter를 static으로 변환 후 해당 값을 반환
@NotNull
public static final String getCookieTopping(@NotNull IceCream $this$cookieTopping){
Intrinsics.checkNotNullParameter($this$cookieTopping, "$this$cookieTopping");
return "Topping - cookie";
}
package org.example.declarations
fun List<String>.getLongestString() { /*...*/}
package org.example.usage
import org.example.declarations.getLongestString
fun main() {
val list = listOf("red", "green", "blue")
list.getLongestString()
}
//FileA.kt
private fun printFileA()="FileA"
fun String.showFileA():String{
return printFileA()//접근 가능
}
//FileB.kt
fun String.showFileB():String{
return printFileA()//접근 불가능
}
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 이때 this는 아래 변수 list를 의미
this[index1] = this[index2]
this[index2] = tmp
}
fun main(){
//변수 list가 수신 객체. 수신 타입은 MutableList<Int>
val list = mutableListOf(1, 2, 3)
list.swap(0, 2)
}
class IceCream(val size:Int){
fun order(){
println("Order Ice Cream")
}
}
fun IceCream.order()=println("아이스크림 주문")
fun main(){
val iceCream= IceCream(200)
//기존 함수 메서드인 Order Ice Cream 출력
iceCream.order()
}
class Host(val hostname: String) {
fun printHostname() { print(hostname) }
}
class Connection(val host: Host, val port: Int) {
fun printPort() { print(port) }
fun Host.printConnectionString() {
printHostname() //Host.printHostname() 호출(확장 수신자)
print(":")
printPort() // Connection.printPort() 호출(디스패치 수신자)
}
fun connect() {
/*...*/
host.printConnectionString() // 확장 함수 호출
}
fun Host.getConnectionString() {
this.toString() // Host.toString() 호출(확장 수신자)
this@Connection.toString() // Connection.toString() 호출(디스패치 수신자)
}
}
fun main() {
Connection(Host("kotl.in"), 443).connect()
// 에러 발생. Connection 외부에서 확장 함수 호출 불가능
//Host("kotl.in").printConnectionString()
}
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
//실제 인스턴스는 Rectangle이지만 정적 타입은 Shape.
//정적 바인딩이기에 런타임 타입인 Shape이 아닌 Retangle 출력
printClassName(Rectangle())//Shape
//오버라이딩
open class Parent{
open fun print(){
println("Parent")
}
}
fun Parent.say()=println("Hello Parent")
class Child: Parent() {
override fun print() {
super.print()
}
/*오버라이딩 불가능
override fun say() {
super.print()
}*/
}
//오버로드
open class Parent{
open fun print(){
println("Parent")
}
}
fun Parent.print(str:String)= println("$str Parent")
fun main(){
val parent= Parent()
parent.print()
parent.print("Thank you")
}
class MyClass {
companion object { }
}
fun MyClass.Companion.printCompanion() { println("companion") }
fun main() {
MyClass.printCompanion()
}
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
//컬렉션
fun List<Int>.sumEven(): Int = this.filter { it % 2 == 0 }.sum()
//일급 컬렉션
data class Name(val value: String)
class NameList(val names: List<Name>)
fun NameList.toCommaSeparated(): String = names.joinToString { it.value }
❓❓❓
일급 컬렉션이란 컬렉션을 직접 노출하지 않고 컬렉션을 감싼 별도의 클래스로 포장(wrapping)하는 설계 방식. 로직 캡슐화와 불변성 보장 가능
fun String.isEmail(): Boolean = this.matches(Regex("^[A-Za-z0-9+_.-]+@(.+)$"))
fun Any?.toString(): String {
if (this == null) return "null"
// null 확인 이후에 'this'는 자동으로 non-nullable 타입으로 캐스팅
// 아래의 toString() 호출은 기존의 Any 클래스의 멤버 함수로 해석
return toString()
}
fun <T> List<T>.applyIf(condition: Boolean, block: List<T>.() -> List<T>): List<T> =
if (condition) this.block() else this
val result = listOf(1, 2, 3).applyIf(true) { filter { it > 1 } }
fun <T> Response<T>.isSuccessWithBody(): Boolean
= this.isSuccessful && this.body() != null
fun View.setVisibleIf(condition: Boolean) {
visibility = if (condition) View.VISIBLE else View.GONE
}
해당 예제 코드들의 코틀린 공식 문서와 ChatGPT의 도움을 받아 작성되었습니다
참고
- https://kotlinlang.org/docs/extensions.html#scope-of-extensions
- https://todaycode.tistory.com/176
- https://velog.io/@minju0426/Kotlin-%ED%99%95%EC%9E%A5%ED%95%A8%EC%88%98%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B2%95how-to-use-Extension-Function
- https://jinn-blog.tistory.com/17
- https://best-coding.tistory.com/75?category=1220664