数据结构考研(数据结构考研真题)

数据结构考研,数据结构考研真题

你编写的是数据结构还是对象

写Java的人都会自豪的说我是面向对象编程(或许没有那么自豪)。但我们在实际大型网络项目中进行开发的时候,我们最经常编写的各种对象,他们设计的是否真的妥当。

如果你学过C语言的话应该知道,C语言中允许用户自己指定多个数据并用struct关键字将数据组合为一个整体,而称这种数据结构为结构体。,例如定义一个学生信息结构体:

struct student
{
char name[20];
int age;
};

我们不难发现因为C语言是面向过程的,结构体中只能有各种数值,并没有可以执行的函数。那么,如果我们换成java来创建一个学生对象呢?看起来可能是这样的:

public class Student {
public char[] name;
public int age;
}

如果我们用默认的无参构造方法去尝试实例化一个Student类,那么显然我们会得到一个student对象。那么问题就出现了,我们在Java中创建的这个student对象其本质上和C语言中的student结构体似乎没有区别,甚至在访问其中的成员时都是直接使用的student.num。

而在实际工作中我们一般不直接将Student类中的属性设置为公共类型(public),而是为其添加了赋值器与取值器(get()和set()方法),例如:

public class Student {
private char[] name;
private int age;
public char[] getName() {
return name;
}
public void setName(char[] name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

或者我们使用Lombok来简化编写:

@Data
public class Student {
private char[] name;
private int age;
}

但上面两种的写法本质上是一样的。他们的改进是将将属性设置为了私有的(private),进而让其他人不能直接依赖这些变量,而是通过赋值器与取值器来进行。一般来说这样做有两个好处:

  • 有利于代码的可维护性。可以方便地在对属性访问前后添加必要的操作。
  • 可以在对类进行继承时通过方法覆盖而隐藏属性。

尽管理想如此,但我们会发现很多时候都是在无继承的情况下,使用了不加额外操作的赋值器与取值器。而这样的赋值器与取值器由于会直接将私有变量暴露出去,所以其本质上与public的属性功能是一样的(此处不讨论框架依赖等情况),进而就会发现它与C语言的结构体也是一致的。也就是说:

如果类中只有私有属性与无额外功能的赋值器与取值器,那么我们认为这样的类本质上只是数据结构,而非对象。

对象的意义

上文说到,如果直接将对象中的私有属性暴露出来外部的话,那它本质上是一个数据结构,而非对象。那我们在设计对象的时候应该考虑什么呢?

屏蔽细节。

我认为,对于对象的设计,核心的理念就是屏蔽对象内部的细节,使用户无需了解数据的具体实现就能操作数据本体。而这些操作的“方法“便是由数据提供的。举个例子:

public class PointA{
public double x;
public double y;
}
public interface PointB{
double getX();
double getY();
void setCartesian(double x, double y);
double getR();
double getTheta();
void setPolar(double r, double theta);
}

我们可以看出,PointA类提供了一个数据结构用于描述一个点。而PointB接口则是提供了两组方法,使得用户可以按照极坐标系或者笛卡尔坐标系来设置数据内容。

对于PointA来说,不论你实际在处理坐标系的时候是哪种坐标系,你同样可以直接操作其中的属性来执行逻辑。但由于暴露了实现,所以在使用PointA的时候,我们必须要了解多个概念,例如:

  • PointA中的所有属性:x、y
  • x与y必须同时存在。

我们在使用PointA的时候,因为使用了其中暴露出来的实现,所以就只有了解了实现的内容后才能进行类的使用。当前只有两个属性,而如果是一个拥有大量属性的类,则这种情况会导致业务开发效率降低很多。

而再看PointB,其本身已经是抽象出来的接口了,接口本身也没有额外的属性。尽管没有属性,但是一组去值器也准确无误地呈现了一种数据结构。但是你不用关心其底层是用一个x,y实现的,还是其他方法实现的。而进一步的,PointB还通过方法完整的描述了数据的存取逻辑:需要原子性的设置两个数据,但是取值的时候可以分别进行获取(PointA也是这个逻辑,但是在类中无法体现出来)。

同样的,我们再看一下下面的例子:

public class counterA{
double getCurrentCount();
double getTotalCount();
}
public class counterB{
double getPercentage();
}

两个例子看起来counterA中我们可以获取更全的数据,但是如果我们只关心进度的话,那么countB中的方法则是正好满足我们需要的。实际上counterA中的数据,我们看起来是取值器,而即便我们拿出来了,数据是否正常也是我们需要关心的,而countB的话则只提供了一个抽象的方法,而我们并不需要知道方法的实现细节。所以:

对于对象设计来说,并不直接根据模型进行字段设计再添加上赋值器、取值器就可以了的。仍旧要需要提供最合适的、最少细节的方法给用户。

应该用哪个呢

尽管上文似乎是说:数据结构不太好。但事实上,我们可以换一个角度来描述:

  • 数据结构将数据细节暴露出来了,没有提供方法。
  • 对象提供了首相方法,但是数据在抽象备好。

简单来说就是,数据结构没方法,对象无法拿到细节。那么根据两种数据类型的设计方式就可以进行确定。

面向过程

由于数据结构没有方法,则我们在使用数据结构的时候,不同的数据结构都是直接从中取值,然后再进行业务流程的处理。但相对来说,由于数据结构中没有方法,当希望添加一种新的处理方式的时候,那我们就可以在进行“面向过程编程”的部分添加一个新的方法,而这方法的添加则不需要对已有的数据结构进行调整。但是相反的,这种方法处理的时候不便于添加新的数据结构,因为需要修改所有的方法。

面向对象

而如果是面向对象编程,则我们可以通过类的继承关系,在核心方法中使用多态来实现功能。而一般来说,如果要新增一个子类,则我们就新的实现接口就可以。但是如果我们是要新增接口方法,则要将所有的子类都进行调整。

从上述分析来看,面向对象与面向过程两种方式互为补充。在任何一个复杂的心痛中,我们都不可能只增加子实现类或者只新增方法能力。所以我们就要评估自己项目的变动方向:

  • 如果需求导致方法调整比较多,则可以尝试面向过程编程。
  • 如果需求导致新增子类比较过,则可以用面向堆成编程。

或者说,面向对象不容易处理的其实面向过程就能落地,反之亦然。

最后

作为Java的开发人员都会说自己是面向对象编程,但事实上很多场景下的对象本质上是数据结构。对象可以在功能调用的时候屏蔽细节,但在实际的开发中,使用数据结构的方式也是有实际的优势应用场景的。

数据结构考研(数据结构考研真题)