簡體   English   中英

具有通用 class 的 Class 構造函數

[英]Class constructor with generic class

在我的應用程序中,我有泛型類,我必須根據這些泛型類構造新對象。 是否可以獲得處理子類的通用構造函數?

我會更容易理解代碼:

例如,我有一個有兩個孩子的父類

open class Fruit (
   val name: String
)

class Apple (
   name: String,
   val count: Int
) : Fruit(name) {

   constructor(name: String) : this(name, 0)

}

class Strawberry (
   name: String,
   val weight: Float
) : Fruit(name) {

   constructor(name: String) : this(name, 0f)

}

如您所見,我的 3 個類具有相同的構造函數(只有一個名稱)。 現在我有一個 class,它包含一個通用的 class 水果。 在這個 class 中,我想要創建新水果的方法,而不關心它是水果、蘋果還是草莓:

class Farmer<GenericFruit: Fruit>(val name: String, val age: Int) {

   var cart = arrayListOf<GenericFruit>()

   fun cultivate() {

      for (_ in 1..10) {
         val fruit = cultivateFruit()
         cart.add(fruit)
      }

   }

   fun cultivateFruit() : GenericFruit {

      // Here call constructor of GenericFruit which just the parameters "name"
      val fruit = GenericFruit(name = "name of the fruit")

      return fruit
   }

}

我試過在這樣的方法cultivateFruit中使用reified

inline fun <reified T: GenericFruit> cultivateFruit() : GenericFruit? {

   val constructor = T::class.constructors.find { 
      it.parameters.size == 1 && it.parameters.first().name == "name"
   }
   
   return constructor?.call("name of the fruit")
}

但問題是,現在我需要在方法中使用cultivate ,而 Kotlin 不會讓我這樣做:

fun cultivate() {

   for (_ in 1..10) {
      val fruit = cultivateFruit<GenericFruit>() // Here there is an error, GenericFruit can't be called
      cart.add(fruit)
   }

}

問題是我沒有引用通用構造函數。 我什至沒有在cultivateFruit中訪問 class T 我知道在 swift 上可以實現這一點,我想知道 Kotlin 是否也可以?

最誠實的做法是在static Map Class<? extends Fruit> Class<? extends Fruit> 該名稱也是 class 級別常量,因此不應是構造函數參數。 構造函數應該是每個子 class 的默認構造函數。

public class Fruit ...
    public String getName() {
        return fruitClasses.get(getClass());
    }

    private static<String, Class<? extends Fruit>> fruitClasses = new HashMap<>();

    protected static register(String name, Class<? extends Fruit> fruitClass) {
        Objects.requireNull(fruitClasses.put(name, fruitClass));
    }

    public static Fruit createFruit(String name) {
        Class<? extends Fruit> type = fruitClasses.get(name);
        if (type == null) {
            throw new IllegalArgumentException(
                    "No fruit class registered for " + name);
        }
        return type.getConstructor().newInstance();
    }
}

class Apple extends Fruit {
    static { Fruit.register("Apple", Apple.class); }

在這里,每個 Fruit child class 都負責自己的注冊。

傳統上也可以使用FruitFactory來完成,這對每個人來說似乎都更簡單一些。 缺點是 FruitFactory 要么需要導入每個子 class,要么與上面的Fruit沒有區別。

使用 java 中的新sealed類,您可以定義子類的封閉域,並將它們全部列出。 因此,您可以在 Fruit 本身中注冊所有類。

最后但並非最不重要的一點可能有人想知道 inheritance,多個孩子,是否是一個好主意。 還可以想到動態查找的功能、特性:

public interface JuicePressable {
    float press();
}

public interface HasSeeds {
    int seeds();
    void removeSeeds();
}

Optional<JuicePressable> jp = fruit.as(JuicePressable.class);
jp.ifPresent(fr -> ... fr.press() ...);

fruit.as(HasSeeds.class).ifPresent(fr -> fr.removeSeeds());

這里的基本原理是,如果您有分類處理,並且需要過濾特定操作,並且操作類別的數量是開放的:然后創建功能查找。

Kotlin 回答。

由於無法具體化 class 類型,因此您的 Farmer 需要有一個屬性持有對 class 類型的引用才能使用反射來創建水果。 您可以添加一個配套的 object reified function 偽構造函數,以避免必須顯式傳遞 class 類型。

class Farmer<T: Fruit>(private val fruitClass: KClass<T>, val name: String, val age: Int) {

    companion object {
        inline fun <reified T: Fruit> Farmer(name: String, age: Int) = Farmer(T::class, name, age)
    }

    private val fruitConstructor = fruitClass.constructors.find {
        it.parameters.size == 1 && it.parameters.first().name == "name"
    } ?: error("Fruit class must have a constructor with a single argument of \"name\"")

    var cart = arrayListOf<T>()

    fun cultivate() {
        repeat(10) {
            val fruit = cultivateFruit()
            cart.add(fruit)
        }
    }

    private fun cultivateFruit(): T {
        return fruitConstructor.call("name of the fruit")
    }

}

就個人而言,我不想依靠反射來解決這個問題。 您可以為水果的構造函數添加構造函數參數。 在調用站點上不太方便,但是您將進行編譯時檢查,因此代碼會更加健壯。

class Farmer<T: Fruit>(private val fruitCreator: (String)->T, val name: String, val age: Int) {
    var cart = arrayListOf<T>()

    fun cultivate() {
        repeat(10) {
            val fruit = cultivateFruit()
            cart.add(fruit)
        }
    }

    private fun cultivateFruit(): T {
        return fruitCreator("name of the fruit")
    }

}

構造 Farmer 時,可以將構造函數作為參數傳遞:

val strawberryFarmer = Farmer(::Strawberry, "Foo", 40)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM