创建对象后定义其子类型

时间:2018-10-30 19:25:23

标签: java c# oop

假设我有以下课程:

public class Animal {
  public string name;
  public int age;
  public bool canBark;
}

public class Dog : Animal {
  }

并像这样实例化一个Animal

Animal a = new Animal();

在创建此实例后,是否存在将其向下转换为Dog实例的情况?还是我在创建Animal时被迫意识到自己是Dog?我遇到这个问题的原因是因为我的实例是通过调用工厂类创建的,该工厂类返回基于我提供的JSON文件的类型。通过查看该文件中的几个看似无关的字段来计算canBark属性值。通过查看Dog字段(而不是那些JSON字段)来确定某事物是canBark似乎更有意义。

public class AnimalFactory{
  public static Animal Create(JObject json){
    Animal a = new Animal();
    a.canBark = (json["someField"]["someOtherField"].ToString() == "abc")
    .....
}

我现在通过在Dog类中有一个构造函数来解决此问题,该构造函数将Animal作为其参数,并将该实例变量的值简单地分配给它自己。但这不是一种非常灵活的方法,因为每次添加字段时,我都需要在每个子类构造函数中添加一行。

那么,通过将决策转移到实例创建中来确定类型的最佳方法是吗?

4 个答案:

答案 0 :(得分:5)

  

在创建此实例后,是否有将其转换为Dog实例的方法?

不。在Java或.NET中,构造后永远不能更改对象的类型。您需要更改工厂代码以创建正确的类的实例。目前尚不清楚canBark部分与其余问题之间的关系,但从根本上讲,您需要具有 somewhere 来决定根据JSON内容创建哪种对象-或更改您的设计,以便在所有情况下都可以使用相同的对象类型。

答案 1 :(得分:2)

您可以使用具有Animal返回类型但创建并返回Dog的工厂方法。但是创建Animal后不能将其转换为Dog

还请注意,真正的转换和类型转换不是一回事。即使在C#中,两者的转换运算符相同。如果您有变量Animal a = new Dog();,则可以进行有效的强制转换Dog d = (Dog)a;。但是a必须引用Dog对象。如果为其分配了Cat,则强制类型转换将引发异常。

如果您确实想将Animal转换为Dog,您能做的最好的事情就是在Dog上添加一个接受动物作为参数(C#)的构造函数:< / p>

public Dog(Animal a)
{
    name = a.name;
    age = a.age;
    canBark = true;
}

然后您可以使用以下方法创建一条狗

Animal a = new Animal();
// Initialize animal

Dog dog = new Dog(a);

但是如果不创建一个新对象就不能这样做。因此,从工厂开始就创建正确的类型:

public class AnimalFactory
{
    public static Animal Create(JObject json)
    {
        string animalType = Get animal type string from json;

        Animal a;
        switch (animalType) {
            case "dog":
                var dog = new Dog();
                // Fill dog specific stuff.
                a = dog;
                break;
            case "cat":
                var cat = new Cat();
                // Fill cat specific stuff.
                a = cat;
                break;
            default:
                return null;
        }
        // Fill stuff common to all animals into a.
        return a;
    }
}

直接实例化Animal类可能也没有任何意义。因此,我将其声明为抽象。


您还可以在动物类中使用带有JObject参数的构造函数来委派字段的初始化。

public abstract class Animal
{
    public Animal(JObject json)
    {
        // Initialize common fields.
    }

    public string name;
    public int age;
    public bool canBark;
}

public class Dog : Animal
{
    public Dog(JObject json)
        : base(json) // Pass the json object to the Animal constructor
    {
        // Initialize dog specific fields.
    }
}

然后工厂变成

public class AnimalFactory
{
    public static Animal Create(JObject json)
    {
        string animalType = Get animal type string from json;
        switch (animalType) {
            case "dog":
                return new Dog(json);
            case "cat":
                return new Cat(json);
            default:
                return null;
        }
    }
}

答案 2 :(得分:1)

  

动物a = new Animal();

您绝对不应该这样做,因为在任何非学术环境中,Animal都是抽象类型。

  

在创建此实例后,是否有将其转换为Dog实例的方法?

这很明显,但是new分配了内存。如果您告诉它是为Animal分配内存,但后来又确定它实际上是Dog,则无法添加到分配的内存中。

换句话说,你就是你。如果明天您决定要成为猴子而不是(大概是)人类,那您就不会真正成为猴子。

如果您根本不了解虚拟函数的工作原理,可能不太明显的是,虚拟表是在new期间初始化的,因此编译器需要知道在该点上要分配哪些函数指针创造力。

  

基于我提供的JSON文件的类型

这就是字典或动态对象(在C#中为ExpandoObject)的用途。或者更好的是,使用臭名昭著的Newtonsoft JSON library等适合您的库来为您处理此问题。

答案 3 :(得分:0)

在Java和C#等语言中,对象的类型是在创建对象时确定的。您可以声明一个Animal引用,然后将其指向一个Dog,但是Dog对象本身将始终是一个Dog。对象的用户可能不知道其实际类型,如果不知道,通常会更好。

Animal a = new Dog();
a.onOwnerHome();

但是您对自己的问题有所了解。您在教科书中经常看到的Animal示例的基本问题是,它们往往是非常糟糕的OOP。他们将实际上应该作为数据的事物建模为行为,而学生则错过了思考应该将其建模为行为的事物的机会。