1、面向对象编程

从崛起的智人到被苹果砸中的牛顿再到现在的你,一直在思考“这是个怎么样的世界?”

一、世上本没有苹果

看到一张苹果的图片,脑海里自然浮现“图片上的是一个苹果”,为什么会称苹果为‘苹果’?

世界上所有的物质都是客观存在的,基于事物固有的特性,人类用自己方式将其抽象为具体的概念并用一些媒介表达了出来,这些媒体如语言、绘画、手语等。

将客观存在事物抽象为我们自己的概念,便能方便日常的交流和对事物的研究利用。对事物进行抽象定义的质量越高,我们使用起来越方便。在日常生活中,当提及到这些事物时我们对这些事物的特征、功能也总是‘心中有数’。

我们解决问题的能力直接取决于我们对事物抽象的质量。

二、使用计算机来表达事物的抽象

计算机也是我们表达和使用抽象的一种有效媒介。

进行程序设计的目的就是解决实际生活中一些的问题,程序总是和实际息息相关的。将实际问题中元素用计算机的方式来进行抽象定义,然后利用这些抽象来解决问题。这样我们直面的便问题空间中的抽象,在解决问题是基于问题的结构,而不是基于计算机结构。

不使用面向对象时,可能写出下面的代码(伪代码):

/*将一个用户信息保存到文件*/
userName = "xxxx";
userAge = "18";
userTel = "11111";
fileName = "xxxxxx";
// 保存信息到文件
file = open(fileName);
file.write(userName);
file.write(userAge);
file.write(userTel);
file.close();

这样的代码用户相关信息在码层面并没有提现出强烈的相关,他们都只是一个个独立的变量。只有我们自己知道他们是‘一起’的,并实时维护他的关系。在保存信息到文件时,总时要打开、写入、关闭,对于很多个信息就繁琐且复杂。

使用面向对象来解决这个问题,先将用户信息抽象出来并将抽象的定义的类放到一个单独的文件中:

class UserInfo {
    name;
    age;
    tel;
    FILE_NAME = "xxxxxx";
    
    function saveUserInfo() {
        file = open(FILE_NAME);
        file.write(userName);
        file.write(userAge);
        file.write(userTel);
        file.close();
    }
}

这里,用户的各个信息一目了然的他们是‘一起’的,并且将写的操作也进行了抽象封装。在解决问题的代码文件中使用定义好的抽象:

userUnfo = new UserInfo();
userInfo.name = "xxx";
userInfo.age = 18;
userInfo.tel = "11111";
userinfo.saveUserInfo();

这一部分代码才是我们真正需要的用来解决问题的代码。这里我们不必时刻小心的维护用户信息各个部分是'一起'的,在这他们都属于UserInfo的属性,保存文件时也不必再考虑其实现的细节,这些细节我们已经做好了封装,在这里只需要使用就行。

利用计算机来对事物进行抽象并使用便是面向对象编程。

三、面向对象的三个特性

1、封装
将水烧开,我们需要热水器和电。将水倒入插电的热水器,打开开关将水烧开。在这个过程中我们并不关心热水器的具体实现,也不关心电是这产生的等等,这些所有的东西都是“封装”好的,我们只使用好这些东西就行。

编程来解决这个问题时,何不妨同样先将热水器等的属性和功能进行封装。然后在使用这些封装好的元素来解决问题,就如同实际中一样自然流畅。而不是在每个步骤都调用计算机相关的特性来实现,这样会让整个问题和计算机的实现混合不清。

类与对象:

面向对象中用类来描述事物的属性和功能,对事物进行抽象便是抽象为一个类。
对象是类的一个具体实例。如某天看到的一只鸟,这只鸟便是鸟类的一个具体实例。

通过定义类将问题空间中元素的进行了封装,然后实例化类的对象来为问题的求解提供服务。

类也是自定义的数据类型:

一个类定义完成后便是一个我们自己定义的数据类型。像创建内置类型的变量一样创建类的变量(即类的对象),然后操作这些对象。每个对象都有其类的特性,而每一个对象有是独立的一个变量。

生活中使用程序提供的服务来解决问题,而程序的又调用各个对象提供的服务来来实现。这些对象和实际问题中的各个元素一样,通过调用这些元素完成特定的功能来解决问题。对象的调用便是调用对象的方法,每一个方法满足了特定的需求。

在面向对象中一切都是对象,每个对象提供服务或数据给其他对象,对象之间互相调用和传递数据来解决问题

2、继承

一个新类和原有类具有相似的功能,只是添加了部分新功能或部分功能不一样,我们又得重新定义重复的功能,这样显得很麻烦,代码不具备复用性。 这类需求在面向对象中使用继承来处理。

通过继承新的类便具备了原有类的功能和特性的同时又可以进行自己的扩展创建出一个新类。被继承的类称为基类,继承基类的新类称为导出类。通常将一些共性和核心创建为基类,再通过继承来创建新不同实现的新类。

继承时,导出类继承了基类所有的特性和功能,这也就意味着所有对基类的调用,在导出类中同样也具有并可以调用,如此意味着导出类与基类是同一的类型。但基类与导出类却不是同一类型,因为对导出类添加的新特性基类并不具备。

所以可以将导出类的实例对象赋给基类,作为一个基类对象使用。但这时是并不可以访问导出类的新方法,毕竟导出类同基类是同一类型,但基类同导出类不是同一类型,因为导出类会添加或修改类功能。
3、多态
你有很多部手机,但每一部手机的充电器都是不一样的,当其中一部手机需要充电的时,你对助理说“帮我冲下电”,助理自然会去找合适充电器,不必告诉助理手机是什么型号去找什么样的充电器。

将这些手机进行封装

先将手机共有的特性封装为一个Phone基类,基类包括充电这个共性功能。然后继承这个类并覆盖基类的充电方法,来实现每一类型的充电的具体实现。

生活中我们说手机充电,这里手机是一个大的类型,但我们知道该用那个一个充电器。
既然在继承时导出类和基类是同一个类型,那么当我们把一个派生类对象赋给基类时,对于那些覆盖了基类的功能,同样也应该知道在需要时去调用派生类的实现。

Phone p = new Apple();
p.charge();

通过继承新加了Apple手机的类,然后创建Apple类并将它赋值给了Phone类型,那么当调用充电时自然应该去调用Apple自己的充电方法。

根据对象具体实现取去查找其对应的方法,这就是多态。

当然多态对于产生覆盖的方法才有意义,毕竟虽然导出类和基类可以视为统一类型是因为导出类继承了基类的全部,但基类却并不拥有导出类的新特性。所以在一个基类对象上调用导出类的新功能者并没有什么意义,也不符合现实。毕竟在生活中我们可以说交通工具载货,但并不会说交通工具起飞,而是说飞机起飞。但对于导出类覆盖基类的方法,导出类和基类都有这样功能,只是实现不同,所以多态在此时显得有意义且必要。

*建议

  1. 如果新的类只是单纯的继承而没有添加任何新的特性,这就意味着导出类和基类属性和方法完全相同,然而这样并没有什么特别的意义,建议导出类与基类存在一定的差异。

产生差异:在导出类中添加新的特性或覆盖

  1. 在需要时才使用继承,处处使用继承是不合理的且会导致设计过分复杂和难以维护。是否使用继承:导出类与基类是否为“是一个的关系”,并具有意义,列如一个圆是一个几何”

相关推荐