>  기사  >  Java  >  위임을 사용하여 Kotlin에서 믹스인(또는 특성) 구현

위임을 사용하여 Kotlin에서 믹스인(또는 특성) 구현

Mary-Kate Olsen
Mary-Kate Olsen원래의
2024-10-18 20:11:031021검색

Implementing Mixins (or Traits) in Kotlin Using Delegation

(내 웹사이트에서 이 기사를 프랑스어로 읽어보세요)

객체 지향 프로그래밍에서 Mixin은 하나 이상의 사전 정의된 자율 기능을 클래스에 추가하는 방법입니다. 일부 언어는 이 기능을 직접 제공하는 반면 다른 언어는 믹스인을 코딩하는 데 더 많은 노력과 타협이 필요합니다. 이번 글에서는 위임을 사용하여 Kotlin에서 Mixin을 구현하는 방법을 설명합니다.

  • 목적
    • "믹스인" 패턴의 정의
    • 특성과 제약
  • 구현
    • 구성별 순진한 접근
    • 상속 사용
    • Mixin의 상태를 유지하기 위한 위임
    • 최종 구현
  • 제한사항
    • 감사 가능
    • 관찰 가능
    • 엔티티/아이덴티티
  • 결론

목적

"믹스인" 패턴의 정의

믹스인 패턴은 싱글톤이나 프록시와 같은 다른 디자인 패턴만큼 정확하게 정의되지 않습니다. 문맥에 따라 용어의 의미에 약간의 차이가 있을 수 있습니다.

이 패턴은 다른 언어(예: Rust)에 존재하는 "특성"과 유사할 수도 있지만 마찬가지로 "특성"이라는 용어는 사용되는 언어에 따라 반드시 동일한 의미는 아닙니다.1.

다음은 Wikipedia의 정의입니다.

객체 지향 프로그래밍에서 믹스인(또는 믹스인)은 다른 클래스의 상위 클래스가 될 필요 없이 다른 클래스에서 사용하는 메서드를 포함하는 클래스입니다. 이러한 다른 클래스가 믹스인의 메서드에 액세스하는 방식은 언어에 따라 다릅니다. 믹스인은 때때로 "상속"되기보다는 "포함"되는 것으로 설명됩니다.

믹스인 기반 프로그래밍 주제에 대한 다양한 기사(2, 3, 4)에서도 정의를 찾을 수 있습니다. 이러한 정의는 또한 고전 상속에서 제공하는 부모-자식 관계(또는 is-a) 없이 클래스 확장이라는 개념을 가져옵니다. 이는 Kotlin(또는 Java)에서는 불가능하지만 믹스인 사용의 관심사 중 하나로 제시되는 다중 상속과도 연결됩니다.

특성 및 제약

이러한 정의와 밀접하게 일치하는 패턴을 구현하려면 다음 제약 조건을 충족해야 합니다.

  • 클래스에 여러 믹스인을 추가할 수 있습니다. 우리는 객체 지향 컨텍스트에 있으며 이 제약 조건이 충족되지 않으면 상속과 같은 다른 디자인 가능성에 비해 패턴이 거의 관심을 끌지 못할 것입니다.
  • Mixin 기능은 클래스 외부에서도 사용할 수 있습니다. 마찬가지로 이 제약 조건을 무시하면 패턴은 간단한 구성으로 달성할 수 없는 결과를 가져오지 않습니다.
  • 클래스에 믹스인을 추가한다고 해서 클래스 정의에 속성과 메서드를 추가해야 하는 것은 아닙니다. 이러한 제약이 없으면 믹스인은 더 이상 "블랙박스" 기능으로 볼 수 없습니다. 클래스에 추가하기 위해 믹스인의 인터페이스 계약에 의존할 수 있을 뿐만 아니라 그 기능을 이해해야 합니다(예: 문서를 통해). 클래스나 함수를 사용하듯이 믹스인을 사용할 수 있게 하고 싶습니다.
  • 믹스인은 상태를 가질 수 있습니다. 일부 믹스인은 해당 기능을 위해 데이터를 저장해야 할 수도 있습니다.
  • 믹스인은 타입으로 사용될 수 있습니다. 예를 들어, 주어진 믹스인을 사용하는 한 어떤 객체든 매개변수로 취하는 함수를 가질 수 있습니다.

구현

구성별 순진한 접근 방식

클래스에 기능을 추가하는 가장 간단한 방법은 다른 클래스를 속성으로 사용하는 것입니다. 그런 다음 이 속성의 메서드를 호출하여 믹스인의 기능에 액세스할 수 있습니다.

class MyClass {
    private val mixin = Counter()

    fun myFunction() {
        mixin.increment()

        // ...
    }
}

이 방법은 Kotlin 유형 시스템에 어떤 정보도 제공하지 않습니다. 예를 들어, Counter를 사용하여 개체 목록을 갖는 것은 불가능합니다. Counter 유형의 객체를 매개변수로 취하는 것은 관심이 없습니다. 이 유형은 믹스인만을 나타내므로 객체는 애플리케이션의 나머지 부분에 쓸모가 없을 것입니다.

이 구현의 또 다른 문제는 이 클래스를 수정하거나 믹스인을 공개하지 않으면 클래스 외부에서 믹스인 기능에 액세스할 수 없다는 것입니다.

상속의 사용

믹스인이 애플리케이션에서 사용할 수 있는 유형도 정의하려면 추상 클래스에서 상속하거나 인터페이스를 구현해야 합니다.

추상 클래스를 사용하여 믹스인을 정의하는 것은 불가능합니다. 단일 클래스에서 여러 믹스인을 사용할 수 없기 때문입니다(Kotlin에서는 여러 클래스에서 상속하는 것이 불가능합니다).

이렇게 하면 인터페이스를 사용하여 믹스인이 생성됩니다.

interface Counter {
    var count: Int
    fun increment() {
        println("Mixin does its job")
    }
    fun get(): Int = count
}

class MyClass: Counter {
    override var count: Int = 0 // We are forced to add the mixin's state to the class using it

    fun hello() {
        println("Class does something")
    }
}

이 접근 방식은 여러 가지 이유로 이전 접근 방식보다 더 만족스럽습니다.

  • 믹스인을 사용하는 클래스는 기본 메소드 덕분에 믹스인의 동작을 구현할 필요가 없습니다
  • Kotlin에서는 클래스가 여러 인터페이스를 구현할 수 있으므로 클래스는 여러 믹스인을 사용할 수 있습니다.
  • 각 믹스인은 해당 클래스에 포함된 믹스인을 기반으로 객체를 조작하는 데 사용할 수 있는 유형을 생성합니다.

그러나 이 구현에는 상당한 제한이 남아 있습니다. 믹스인은 상태를 포함할 수 없습니다. 실제로 Kotlin의 인터페이스는 속성을 정의할 수 있지만 직접 초기화할 수는 없습니다. 따라서 믹스인을 사용하는 모든 클래스는 믹스인 작업에 필요한 모든 속성을 정의해야 합니다. 이는 믹스인을 사용하여 이를 사용하는 클래스에 속성이나 메서드를 추가하도록 강제하는 것을 원하지 않는다는 제약 조건을 따르지 않습니다.

따라서 인터페이스를 유형과 여러 믹스인을 모두 사용할 수 있는 유일한 방법으로 유지하면서 믹스인이 상태를 가질 수 있는 솔루션을 찾아야 합니다.

Mixin의 상태를 포함하는 위임

이 솔루션은 믹스인을 정의하는 데 약간 더 복잡합니다. 그러나 이를 사용하는 클래스에는 영향을 미치지 않습니다. 비결은 각 믹스인을 객체와 연결하여 믹스인에 필요할 수 있는 상태를 포함하는 것입니다. 이 객체를 Kotlin의 위임 기능과 연결하여 믹스인을 사용할 때마다 이 객체를 생성할 것입니다.

모든 제약 조건을 충족하는 기본 솔루션은 다음과 같습니다.

class MyClass {
    private val mixin = Counter()

    fun myFunction() {
        mixin.increment()

        // ...
    }
}

최종 구현

구현을 더욱 개선할 수 있습니다. CounterHolder 클래스는 구현 세부 사항이므로 이름을 알 필요가 없다는 점이 흥미로울 것입니다.

이를 달성하기 위해 믹스인 인터페이스의 동반 개체와 "Factory Method" 패턴을 사용하여 믹스인의 상태를 포함하는 개체를 생성합니다. 또한 약간의 Kotlin 흑마법을 사용하므로 이 메소드의 이름을 알 필요가 없습니다.

interface Counter {
    var count: Int
    fun increment() {
        println("Mixin does its job")
    }
    fun get(): Int = count
}

class MyClass: Counter {
    override var count: Int = 0 // We are forced to add the mixin's state to the class using it

    fun hello() {
        println("Class does something")
    }
}

제한사항

이러한 믹스인 구현은 완벽하지 않습니다(제 생각에는 언어 수준에서 지원되지 않으면 완벽할 수 없습니다). 특히 다음과 같은 단점이 있습니다.

  • 모든 믹스인 메소드는 공개되어야 합니다. 일부 믹스인에는 해당 믹스인을 사용하는 클래스에서 사용하도록 의도된 메서드가 포함되어 있고 다른 믹스인은 외부에서 호출하는 경우 더 의미가 있습니다. 믹스인은 인터페이스에서 해당 메서드를 정의하므로 컴파일러가 이러한 제약 조건을 강제로 확인하도록 하는 것은 불가능합니다. 그런 다음 문서나 정적 코드 분석 도구에 의존해야 합니다.
  • Mixin 메서드는 mixin을 사용하는 클래스의 인스턴스에 액세스할 수 없습니다. 위임 선언 시 인스턴스가 초기화되지 않아 믹스인에 전달할 수 없습니다.
interface Counter {
    fun increment()
    fun get(): Int
}

class CounterHolder: Counter {
    var count: Int = 0
    override fun increment() {
        count++
    }
    override fun get(): Int = count
}

class MyClass: Counter by CounterHolder() {
    fun hello() {
        increment()
        // The rest of the method...
    }
}

믹스인 내부에서 사용한다면 홀더 클래스 인스턴스를 참고하세요.

이 글에서 제안하는 패턴에 대한 이해를 돕기 위해 믹스인의 몇 가지 현실적인 예를 소개합니다.

감사 가능

이 믹스인을 사용하면 클래스가 해당 클래스의 인스턴스에서 수행된 작업을 "기록"할 수 있습니다. 믹스인은 최신 이벤트를 검색하는 또 다른 방법을 제공합니다.

class MyClass {
    private val mixin = Counter()

    fun myFunction() {
        mixin.increment()

        // ...
    }
}

주목할 만한

Observable 디자인 패턴은 믹스인을 사용하여 쉽게 구현할 수 있습니다. 이렇게 하면 관찰 가능 클래스는 더 이상 구독 및 알림 논리를 정의하거나 관찰자 목록 자체를 유지할 필요가 없습니다.

interface Counter {
    var count: Int
    fun increment() {
        println("Mixin does its job")
    }
    fun get(): Int = count
}

class MyClass: Counter {
    override var count: Int = 0 // We are forced to add the mixin's state to the class using it

    fun hello() {
        println("Class does something")
    }
}

그러나 이 특정한 경우에는 단점이 있습니다. 통지Observers 메소드는 비공개로 유지하는 것을 선호하더라도 Catalog 클래스 외부에서 액세스할 수 있습니다. 하지만 믹스인을 사용하는 클래스에서 사용하려면 모든 믹스인 메서드를 공개해야 합니다(Kotlin에서 단순화한 구문이 상속처럼 보이더라도 상속이 아니라 합성을 사용하기 때문입니다).

엔터티/신원

프로젝트가 지속적인 비즈니스 데이터를 관리하거나 적어도 부분적으로 DDD(도메인 기반 디자인)를 실행하는 경우 애플리케이션에 엔터티가 포함될 수 있습니다. 엔터티는 종종 숫자 ID 또는 UUID로 구현되는 ID가 있는 클래스입니다. 이러한 특성은 믹스인을 사용하는 것과 잘 어울리며, 그 예를 들어보겠습니다.

interface Counter {
    fun increment()
    fun get(): Int
}

class CounterHolder: Counter {
    var count: Int = 0
    override fun increment() {
        count++
    }
    override fun get(): Int = count
}

class MyClass: Counter by CounterHolder() {
    fun hello() {
        increment()
        // The rest of the method...
    }
}

이 예는 약간 다릅니다. 홀더 클래스의 이름을 다르게 지정하는 것을 방해하는 요소가 없으며 인스턴스화 중에 매개변수를 전달하는 것을 방해하는 요소도 없습니다.

결론

mixin 기술을 사용하면 이러한 기능을 수용하기 위해 클래스를 수정하지 않고도 횡단적이고 재사용 가능한 동작을 추가하여 클래스를 풍부하게 할 수 있습니다. 일부 제한에도 불구하고 믹스인은 코드 재사용을 용이하게 하고 애플리케이션의 여러 클래스에 공통적인 특정 기능을 분리하는 데 도움이 됩니다.

Mixins는 Kotlin 개발자 툴킷의 흥미로운 도구이므로 제약 조건과 대안을 염두에 두고 자신의 코드에서 이 방법을 살펴보는 것이 좋습니다.


  1. 재미있는 사실: Kotlin에는 특성 키워드가 있지만 더 이상 사용되지 않으며 인터페이스로 대체되었습니다(https://blog.jetbrains.com/kotlin/2015/05/kotlin-m12-is-out/#traits 참조). -are-now-인터페이스) ↩

  2. 믹스인 기반 상속 ↩

  3. 클래스 및 믹스인 ↩

  4. 풍미를 사용한 객체 지향 프로그래밍 ↩

위 내용은 위임을 사용하여 Kotlin에서 믹스인(또는 특성) 구현의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.