Mobile Development 11 min read

Kotlin Class Extensions, Companion Objects, Singleton Design, Dynamic Proxy, and Pseudo‑Multiple Inheritance

This article explains Kotlin's class extension functions, highlights pitfalls of static resolution, demonstrates smart casts, describes companion objects and singleton patterns, and shows how to implement dynamic proxies and pseudo‑multiple inheritance with practical code examples for Android development.

Hujiang Technology
Hujiang Technology
Hujiang Technology
Kotlin Class Extensions, Companion Objects, Singleton Design, Dynamic Proxy, and Pseudo‑Multiple Inheritance

4.5 Class Extensions

The author notes that Java developers often write many utility classes (e.g., Apache Commons, Guava) and wishes those utilities were directly available on the classes that need them; Kotlin's class extension functions provide exactly that capability.

4.5.1 Extension Functions

The syntax of an extension function is briefly recalled:

fun Activity.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}

An extension function starts with the fun keyword, followed by the class to extend, a dot, the new method name, its parameters, return type (if any), and the method body.

4.5.2 Beware of Pitfalls

Extension functions are resolved statically; they do not actually add a method to the class. The following example demonstrates that the call is bound to the compile‑time type, not the runtime subtype.

open class Animal {}

class Dog : Animal()

object Main {
    fun Animal.bark() = "animal"

    fun Dog.bark() = "dog"

    fun Animal.printBark(anim: Animal){
        println(anim.bark())
    }

    @JvmStatic fun main(args: Array
) {
        Animal().printBark(Dog())
    }
}

The output is animal , because the extension is resolved based on the declared type Animal , not the actual Dog instance.

4.6.1 Smart Casts and Explicit Casts

Kotlin uses is to check an object's type and as for explicit casts. The language also provides smart casts, which automatically treat a variable as the more specific type after a successful is check.

fun main(args: Array
) {
    var animal: Animal? = xxx
    if (animal is Dog) {
        // animal is treated as Dog here
        animal.bark()
    }
}

4.6.2 Exceptions to Smart Casts

If the variable being smart‑cast is a mutable global, the compiler cannot guarantee its type remains unchanged, so an explicit cast is required.

open class Animal {}

class Dog : Animal() {
    fun bark() { println("animal") }
}

var animal: Animal? = null

fun main(args: Array
) {
    if (animal is Dog) {
        // Must cast explicitly
        (animal as Dog).bark()
    }
}

4.7 Companion Objects

Kotlin lacks static methods; the recommended approach is to use top‑level functions. When a method must be callable without an instance but still needs access to the enclosing class (e.g., factory methods or singletons), it can be placed inside a companion object , which the compiler turns into a generated public inner object.

class StringUtils {
    companion object {
        fun isEmpty(str: String): Boolean {
            return "" == str
        }
    }
}

After compilation the compiler emits a public final class with a public companion object containing the isEmpty method.

public final class StringUtils public constructor() {
    public companion object {
        public final fun isEmpty(str: kotlin.String): kotlin.Boolean {
            /* compiled code */
        }
    }
}

4.8 Singleton Class Design

Companion objects are also useful for implementing singletons. A common pattern is to expose a get() method that returns an instance held by a private static holder class, letting the JVM handle thread‑safe lazy initialization.

class Single private constructor() {
    companion object {
        fun get(): Single {
            return Holder.instance
        }
    }

    private object Holder {
        val instance = Single()
    }
}

4.9 Dynamic Proxy

Kotlin natively supports dynamic proxies, making it easier to replace Java's reflection‑based proxy mechanism. The example shows an Animal interface, a Dog implementation, and a Cat class that delegates its behavior to an Animal instance via by .

interface Animal {
    fun bark()
}

class Dog : Animal {
    override fun bark() { println("Wang Wang") }
}

class Cat(animal: Animal) : Animal by animal

fun main(args: Array
) {
    Cat(Dog()).bark()
}

The output is Wang Wang , demonstrating successful delegation.

4.10 Pseudo‑Multiple Inheritance

Dynamic proxies can also be used to simulate multiple inheritance. The scenario described involves several BaseActivity subclasses needing different network requests. By defining separate interfaces (e.g., Animal , Food ) and a delegate class that implements both, a concrete class can delegate each interface to the same delegate instance.

interface Animal { fun bark() }
interface Food { fun eat() }

class Delegate : Animal, Food {
    override fun eat() { println("mouse") }
    override fun bark() { println("Miao") }
}

class Cat(animal: Animal, food: Food) : Animal by animal, Food by food

@JvmStatic fun main(args: Array
) {
    val delegate: Delegate = Delegate()
    Cat(delegate, delegate).bark()
}

The article concludes the discussion of Kotlin classes and hints that the next chapter will cover closures.

Recommended Reading

Series | Kotlin Basics

Series | Kotlin Simple Project

Series | Kotlin Primer – Chapter 1: Introduction

Series | Kotlin Primer – Chapter 2: Basic Syntax

Series | Kotlin Primer – Chapter 3: Kotlin & Java Interop

Series | Kotlin Primer – Chapter 4: Class Features (Part 1)

Mobile DevelopmentKotlinDynamic ProxySingletonClass ExtensionsCompanion Object
Hujiang Technology
Written by

Hujiang Technology

We focus on the real-world challenges developers face, delivering authentic, practical content and a direct platform for technical networking among developers.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.