简体   繁体   English

具有通用 class 的 Class 构造函数

[英]Class constructor with generic class

In my application I have generic classes, and I have to construct new objects according to these generic classes.在我的应用程序中,我有泛型类,我必须根据这些泛型类构造新对象。 Is it possible to get a generic constructor which handle sub classes?是否可以获得处理子类的通用构造函数?

I'll be more understandable with code:我会更容易理解代码:

I have for exemple a parent classe with two children例如,我有一个有两个孩子的父类

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)

}

As you can see, my 3 classes have the same constructor (with just a name).如您所见,我的 3 个类具有相同的构造函数(只有一个名称)。 Now I have a class, which contains a generic class of Fruit.现在我有一个 class,它包含一个通用的 class 水果。 In this class, I want to have method to create new Fruit, without care about if it's a Fruit, an Apple or a Strawberry:在这个 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
   }

}

I've tried to use reified in the method cultivateFruit like this:我试过在这样的方法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")
}

But the issue is that now I need to now the class in the method cultivate and Kotlin won't let me do that:但问题是,现在我需要在方法中使用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)
   }

}

The issue is that I have no reference to the generic constructor.问题是我没有引用通用构造函数。 I even not have in the cultivateFruit access to the class T .我什至没有在cultivateFruit中访问 class T I know it is possible to achieve that on swift, I'm wondering if is it possible too with Kotlin?我知道在 swift 上可以实现这一点,我想知道 Kotlin 是否也可以?

The most honest would be to have a static Map of String name to the Class<? extends Fruit>最诚实的做法是在static Map Class<? extends Fruit> Class<? extends Fruit> . Class<? extends Fruit> The name is also a class level constant, and thus should not be a constructor parameter.该名称也是 class 级别常量,因此不应是构造函数参数。 The constructor should be the default constructor of every child 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); }

Here every Fruit child class takes care of its own registration.在这里,每个 Fruit child class 都负责自己的注册。

It could also be done traditionally with a FruitFactory , which seems a bit simpler for everybody.传统上也可以使用FruitFactory来完成,这对每个人来说似乎都更简单一些。 The disadvantage being that the FruitFactory either would need to import every child class, or there is no difference with the Fruit above.缺点是 FruitFactory 要么需要导入每个子 class,要么与上面的Fruit没有区别。

With the new sealed classes in java, you define a closed domain of child classes, listing them all.使用 java 中的新sealed类,您可以定义子类的封闭域,并将它们全部列出。 Hence you could register all classes in Fruit itself.因此,您可以在 Fruit 本身中注册所有类。

Last but not least one may wonder whether inheritance, multiple children, is such a good idea.最后但并非最不重要的一点可能有人想知道 inheritance,多个孩子,是否是一个好主意。 One can also think of capabilities, features dynamically looked up:还可以想到动态查找的功能、特性:

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());

The rationale here, that if you have a categorical treatment, and would need filtering for specific actions, and the number of operational categories is open: then create a lookup of capabilities.这里的基本原理是,如果您有分类处理,并且需要过滤特定操作,并且操作类别的数量是开放的:然后创建功能查找。

Kotlin answer. Kotlin 回答。

Since class types can't be reified, your Farmer would need to have a property hold a reference to class type to be able to use reflection to create the fruit.由于无法具体化 class 类型,因此您的 Farmer 需要有一个属性持有对 class 类型的引用才能使用反射来创建水果。 You could add a companion object reified function pseudo-constructor to avoid having to explicitly pass the class type.您可以添加一个配套的 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")
    }

}

Personally, I wouldn't want to rely on reflection for this.就个人而言,我不想依靠反射来解决这个问题。 You could add a constructor parameter for the fruit's constructor.您可以为水果的构造函数添加构造函数参数。 It's a little less convenient at the call site, but you would have compile-time checking so the code would be more robust.在调用站点上不太方便,但是您将进行编译时检查,因此代码会更加健壮。

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")
    }

}

When constructing a Farmer, you can pass the constructor as the argument:构造 Farmer 时,可以将构造函数作为参数传递:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM