Welcome to follow the official wechat account: FSA Full stack action 👋

One, foreword

Design patterns are a guide to solving specific problems in software engineering. It is often said that Java has 23 design patterns, and Kotlin, as a better Java, has many new language features. What improvements can be made to the design patterns commonly used in Java?

Note: This series is a summary of my understanding of the design mode in Chapter 9 of Kotlin Core Programming, so there will be some changes in the summary content. In addition, some chapters are not meaningful, so they are not summarized in this series. If you are interested, you can refer to the original books by yourself.

  • The factory pattern
    • Purpose: Hides the creation logic of an object instance without exposing it to the client.
    • Segmentation: simple factory, factory method, abstract factory.

Two, improve the simple factory

  • Example: Computer manufacturer produces computer (server, home PC)
  • Important: Associated object, operator overloading (invoke)
/** * computer interface **@author GitLqr
 */
interface Computer {
    val cpu: String
}

/** * computer **@author GitLqr
 */
class PC(override val cpu: String = "Core") : Computer
class Server(override val cpu: String = "Xeon") : Computer

/** * Computer type enumeration **@author GitLqr
 */
enum class ComputerType {
    PC, SERVER
}
Copy the code

Simple factories, which typically use singletons (privatized constructors, enforced through class methods), start with the Java version:

/** * Simple Computer Factory (Java version) **@author GitLqr
 */
public static class ComputerFactory {
    private ComputerFactory(a) {}public static Computer produce(ComputerType type) {
        if (type == ComputerType.PC) {
            return new PC();
        } else if (type == ComputerType.SERVER) {
            return new Server();
        } else {
            throw new IllegalArgumentException("There is no other type of computer."); }}}/ / use
Computer pc = ComputerFactory.produce(ComputerType.PC);
Computer server = ComputerFactory.produce(ComputerType.SERVER);
Copy the code

Kotlin already implements singletons at the language level (using the object keyword instead of class), so change the above code to the Kotlin version:

/** * Simple computer Factory (Kotlin version) **@author GitLqr
 */
object ComputerFactory {
    fun produce(type: ComputerType): Computer {
        return when (type) {
            ComputerType.PC -> PC()
            ComputerType.SERVER -> Server()
        }
    }
}

/ / use
val pc = ComputerFactory.produce(ComputerType.PC)
val server = ComputerFactory.produce(ComputerType.SERVER)
Copy the code

The core of this simple factory is to let the ComputerFactory create instances of computers based on different ComputerType parameters. The produce method name doesn’t matter, but with Kotlin’s operator overloading, we can simplify the code even further:

/** * Simple factory improvement: Overload the invoke operator **@author GitLqr
 */
object ComputerFactory {
    operator fun invoke(type: ComputerType): Computer {
        return when (type) {
            ComputerType.PC -> PC()
            ComputerType.SERVER -> Server()
        }
    }
}

/ / use
val pc = ComputerFactory(ComputerType.PC)
val server = ComputerFactory(ComputerType.SERVER)
Copy the code

You might think that ComputerFactory(computertype.pc) is too weird, more like creating a factory instance, and it would be more logical to change it to Computer(computertype.pc), no problem, Simply replace the constructor with a static factory method in the Computer interface. Let’s look at the Java version:

/** * Static factory methods (Java version) **@author GitLqr
 */
interface Computer {
    class Factory{ Public static class Factory
        public static Computer produce(ComputerType type){
            if (type == ComputerType.PC) {
                return new PC();
            } else if (type == ComputerType.SERVER) {
                return new Server();
            } else {
                throw new IllegalArgumentException("There is no other type of computer."); }}}... }/ / use
Computer pc = Computer.Factory.produce(ComputerType.PC);
Computer server = Computer.Factory.produce(ComputerType.SERVER);
Copy the code

Translate the above Java code into the Kotlin version:

/** * Static factory method (Kotlin version) **@author GitLqr
 */
interface Computer {
    companion object Factory{
        fun produce(type: ComputerType): Computer {
            return when (type) {
                ComputerType.PC -> PC()
                ComputerType.SERVER -> Server()
            }
        }
    }
    ...
}

/ / use
val pc = Computer.Factory.produce(ComputerType.PC)
val server = Computer.Factory.produce(ComputerType.SERVER)
Copy the code

Note: The Companion object name default is Companion, which can be customized, but can be omitted if the object name is not specified. Computer. Produce (computertype.pc) can also be used, except in one case. If you are extending properties or methods for a companion object, you must write the custom name of the companion object.

We know that the name of the associated object in Kotlin can be omitted. Combined with operator overloading, the above code can be further simplified as:

/** * Static factory method modified: overload the invoke operator **@author GitLqr
 */
interface Computer {
    companion object {
        operator fun invoke(type: ComputerType): Computer {
            return when (type) {
                ComputerType.PC -> PC()
                ComputerType.SERVER -> Server()
            }
        }
    }
    ...
}

/ / use
val pc = Computer(ComputerType.PC)
val server = Computer(ComputerType.SERVER)
Copy the code

Third, to improve the abstract factory

  • Example: Computer brands manufacture computers
  • Key points: inline functions (inline + reified)

Now we have added the concept of brand manufacturers, such as Dell and Asus. If we use simple factory mode, we need to create n brand manufacturer factory classes, such as DellComputerFactory and AsusComputerFactory. There are many brand manufacturers. The factory needs to be abstracted:

/** * all brands of computer **@author GitLqr
 */
interface Computer
class Dell : Computer
class Asus : Computer

/** * Each brand manufacturer **@author GitLqr
 */
abstract class AbstractFactory {
    abstract fun produce(a): Computer
}

class DellFactory : AbstractFactory() {
    override fun produce(a) = Dell()
}

class AsusFactory : AbstractFactory() {
    override fun produce(a) = Asus()
}
Copy the code

The abstract factory pattern typically provides a method in the abstract factory class to get a concrete factory instance based on parameters. Combined with Kotlin’s overloaded operator properties, this method can be written like this:

/** * Abstract factory: Build factory instances based on method parameters **@author GitLqr
 */
abstract class AbstractFactory {
    abstract fun produce(a): Computer

    companion object {
        operator fun invoke(type: String): AbstractFactory {
            return when (type) {
                "dell" -> DellFactory()
                "asus" -> AsusFactory()
                else -> throw IllegalArgumentException()
            }
        }
    }
}

/ / use:
val factory = AbstractFactory("dell")
val computer = factory.produce()
Copy the code

Note: What does not limit the parameters, can be a string, enumeration, concrete factory instance, etc., in “the Kotlin core programming,” case using parameter is introduced to the specific factory instance, personally, I think it wrong, because the core of the factory pattern is hidden object instances created logic, without the need to expose to the client, So I’m going to use strings instead.

A problem to consider here is that it is not reasonable to use strings to distinguish factory types in real projects because of the problem of hard coding at the call point. Enumeration would be more appropriate, but enumeration would increase the class file. So what is the solution to avoid these two disadvantages? Pass the class type of a specific Computer into the method to determine:

import java.lang.reflect.Type

/** * Abstract factory parameter optimization (Java reflection package) **@author GitLqr
 */
abstract class AbstractFactory {
    abstract fun produce(a): Computer

    companion object {
        operator fun invoke(type: Type): AbstractFactory {
            return when (type) {
                Dell::class -> DellFactory()
                Asus::class -> AsusFactory(a)else -> throw IllegalArgumentException()
            }
        }
    }
}

/ / use
val factory = AbstractFactory(Dell::class.java)
val computer = factory.produce()
Copy the code

Note: In Java, the corresponding class Type is represented by java.lang.reflect.Type, which is usually obtained by class.class, which Kotlin writes as class ::class.java

You should note that this Type is a class in the Java reflection package, and Kotlin is fully Java compatible, so it’s natural to use java.lang.Reflect. Type to represent class types, but Kotlin has a reflection mechanism of its own. You can use kotlin.reflect.KClass to represent the class type, so use the kotlin reflection API version as follows:

import kotlin.reflect.KClass

/** * Abstract factory parameter transfer optimization (Kotlin reflection package) **@author GitLqr
 */
abstract class AbstractFactory {
    abstract fun produce(a): Computer

    companion object {
        // operator fun invoke(type: KClass<*>): AbstractFactory
        operator fun invoke(type: KClass<out Computer>): AbstractFactory {
            return when (type) {
                Dell::class -> DellFactory()
                Asus::class -> AsusFactory(a)else -> throw IllegalArgumentException()
            }
        }
    }
}

/ / use
val factory = AbstractFactory(Dell::class)
val computer = factory.produce()
Copy the code

Note: * in KClass<*> is a wildcard character that accepts any class type; KClass

, on the other hand, uses the out keyword, which is a generic covariant from Kotlin that accepts only the class types of subclasses of Computer.

AbstractFactory(Dell::class) ¶ AbstractFactory

() : AbstractFactory

() So how do you use generics to modify the above method? Remember that Java generics weren’t available until after 1.5. Generic parameter types were erased at compile time for backward compatibility (pure historical baggage =_=), so it’s very difficult to get a generic parameter type in Java. Kotlin’s inline function works with the reified keyword to externalize the generic parameter type, making it easy to get the generic parameter type:

/** * Abstract factory improvement: Build a factory instance based on the generic parameter type (inline + reified generic parameter type) **@author GitLqr
 */
abstract class AbstractFactory {
    abstract fun produce(a): Computer

    companion object {
        inline operator fun <reified T : Computer> invoke(a): AbstractFactory {
            return when (T::class) { T::class displays an error when not using reified
                Dell::class -> DellFactory()
                Asus::class -> AsusFactory(a)else -> throw IllegalArgumentException()
            }
        }
    }
}

/ / use
val factory = AbstractFactory<Dell>()
val computer = factory.produce()
Copy the code

Four, supplement

In [two, improve the simple factory] part mentioned the associated object name problem, here to make a supplement. If Computer is written by another colleague or a third party, it is not convenient for us to directly add new functions to its companion object. In this case, we can use Kotlin’s extension feature to extend methods to the companion object. For example, we can add a function to Computer to determine the Computer type by CPU model:

/** * The associated object extension method (default name) **@author GitLqr
 */
fun Computer.Companion.fromCPU(cpu: String): ComputerType? = when (cpu) {
    "Core" -> ComputerType.PC
    "Xeon" -> ComputerType.SERVER
    else -> null
}

/ / use
val type = Computer.fromCPU(pc.cpu)
Copy the code

If the Companion object in the Computer is named Factory, then the Companion object name must be used instead of the default Companion object name when extending methods to the Companion object:

interface Computer {
    // Customize the associated object name
    companion objectFactory { ... }}/** * The associated object extension method (custom name) **@author GitLqr
 */
fun Computer.Factory.fromCPU(cpu: String): ComputerType? = when (cpu) { ... }

/ / use
val type = Computer.fromCPU(pc.cpu)
Copy the code

Conclusion:

  • When extending a companion object method, if the companion object has a custom name, the custom name is used for the extension.
  • Regardless of whether the associated object has a custom name, the call can be omitted.

If this article is helpful to you, please click on my wechat official number: FSA Full Stack Action, which will be the biggest incentive for me. The public account not only has Android technology, but also iOS, Python and other articles, which may have some skills you want to know about oh ~