概念

谈到面向对象,很多程序员会抛出三个词:封装、继承和多态;或者说抽象、一切都是对象之类的话,然而这会让初学者更加疑惑。下面我想通过一个小例子来说明一下

面向对象一般是和面向过程做对比的,下面是一个简单功能的面向过程和面向对象形式

a = 2
# 要实现 a+2 # 面向过程的方法
sum(a, 3)
# 面向对象的方法(这条在R中还不可执行,只是类似这个意思)
a.sum(3)

看上去只是调用的形式不同,本质没有什么差别,不过当代码量比较大的时候,面向对象就会让我们编程的思路更加清晰。我们先根据上面的形式讲解一下面向过程和面向对象的差别。

差别一:侧重点不同

我们可以把调用函数理解为主谓宾的结构

  • 面向过程就是我们平时调用的函数,通常是这种形式:动作(主语,宾语) ,动作是主要的,主语和宾语分别作为参数传入进行计算
  • 而面向对象的重点则在于这个主语,是这个主语调用了特定的动作,再把宾语作为参数实现运算。

差别二:定义方式不同

  • 面向过程只要定义一个函数sum,指定它有两个参数,然后return二者的和即可
  • 面向对象则复杂一些。首先要定义一个类,在这个类中定义这个类别可以使用的各种方法(可以理解为函数)。然后产生一个类的实例,用这个实例调用这个方法完成计算
  • 举一个通俗的例子,这里的类和我们生活中的类没有什么区别。比如定义一个“鸟”类,再指定这个类有“飞翔”这个方法(即函数、动作)。然后我们抓到一只具体的鸟,这只鸟就是“鸟”类的一个实例,它就可以调用“飞翔”这个方法。如果拉过来一只狗,它不属于“鸟”类,就不能调用“飞翔”这个方法。狗可能属于“狗”类,经过定义就可以调用“叫”这个方法。
  • 在sum的例子中,a是一个整数,它是”整数“这个类的一个实例,你也可以定义b=3则是另外一个实例。它们就具备”整数“这个类可以使用的所有方法,比如加一个数,减一个数之类的。而如果 c="hello",它就属于“字符串”这个类,可以调用字符串的方法
  • 这样做有一个好处,相当于自动对变量进行分类,每一个变量都是一个对象,属于一个特定的类,它可以调用的方法也都是固定的,这样我们拿到一个字符串,就知道可以对它进行哪些处理。

差异三:调用

在实际使用中,如果要对许多对象进行相同的操作

  • 面向过程就是定义一个函数,或者许多函数,然后对每一个对象都套用这个函数即可实现
  • 而面向对象则是定义一个类,指定类的方法,然后每一个对象创建为这个类的实例,实例再调用方法进行计算

当这种需求多起来

  • 面向过程会出现这种情况,今天对A类数据创建了一些函数,明天对B类数据创建了另外一些函数,可能第二天的函数还调用了第一天的函数,函数全部放在一起,拿到数据看哪个好用就拿来用,这样容易乱套。而且每次拿到一个数据都要审视一下之前的这个函数可以处理这个数据吗,处理完可以得到想要的结果吗
  • 而面向对象则每一个类型的对象的方法都放在一起进行管理,都在这个类之下进行定义,这样我们只要看这个对象是这个类的,就自然可以调用这个类的方法。
  • 有的人可能会想,这样面向对象岂不是每一个类都要重新实现那些函数,本来可以调用之前定义好了的函数的。然而类的继承很好地解决了这个问题。比如定义了“鸟”和“狗”类,它们都有“吃”这个方法,但是鸟类可以“飞”,狗类可以“跑”,那么“吃”这个方法其实不需要在两个类中分别定义一次。因为可以先定义一个"动物"类,这个类有“吃”“睡”等方法,然后“鸟”和“狗”类分别从这个类继承下来,便获得了“动物”这个类的所有方法,然后它们可以在自己的类中定义自己特定的方法“飞”“跑”等

接下来我们用python下的例子更具体地说明面向对象的好处

方便函数管理

定义三个类

class bird(object): # 定义鸟这个类

    def __init__(self, name): # 定义类时需要传入的参数,这里指创建实例时需要传入name参数
self.name = name # 将参数赋值给self.name,成为属性,后面定义方法时会调用 def move(self): # 每个类实现一次move方法
print("The bird named",self.name,"is flying") class dog(object): # 定义狗这个类 def __init__(self, name):
self.name = name def move(self):
print("The dog named",self.name,"is running") class fish(object): # 定义鱼这个类 def __init__(self, name):
self.name = name def move(self):
print("The fish named",self.name,"is swimming")

产生实例

bob = bird("Bob") # 给bob这个变量(对象)传入“姓名”name参数
john = bird("John") # 产生两个bird的实例来对比
david = dog("David")
fabian = fish("Fabian")

每个实例分别调用move方法

bob.move() #  1 标号(方便文字说明)
john.move() #
david.move() #
fabian.move() #
# 得到结果如下
# The bird named Bob is flying
# The bird named John is flying
# The dog named David is running
# The fish named Fabian is swimming

首先说明一点,创建实例时要传入这个对象的各种属性值,属性用于存储这个对象的数据,也是一个对象区别于其他对象的标准。比如上面bob这个对象的name是Bob,john则是John,他们正因为传入的参数不同才是两个不同的对象,用相同的方法处理后结果才有差异。我们用同一个函数处理不同的数据也体现在这里,数据都是通过参数传入的,定义这个对象时就把数据传入了,之后再调用方法进行处理,其实是方法在定义时调用了这些属性(也就是你创建实例时传入的数据)。

在这个例子中,move方法其实就通过self.name调用了name属性,运行后"Bob"等才会出现在结果中。实际使用时,传入的参数肯定不会这么简单,可能不会是name这样的姓名最后输出出来而已,而是一个数据框,或者一个非常长的待处理字符串,他们都会被后面的方法调用,实现诸如去除缺失值、描述数据、绘图、建模等方法。

从上面的结果中我们可以看到

  • 234的对比表明,不同对象调用同一个方法move,却得到不同的结果,“鸟”类都是以"the bird named"开头,“狗”类the dog,“鱼”类fish。这是因为它们调用了不同的函数。这样,一个类似的功能,可以定义一个同名的函数方便记忆,同时在不同类中还可以分别得到妥当的处理
  • 12的对比表明,同一类传入不同数据,也可以定制出差异化的结果。这就和一个函数处理不同数据得到不同结果一样

如果使用面向过程的方法,则要如下实现

def movebird(name):
print("The bird named",name,"is flying") def movedog(name):
print("The dog named",name,"is running") def movefish(name):
print("The fish named",name,"is swimming") bob = "Bob"
john = "John"
david = "David"
fabian = "Fabian" movebird(bob)
movebird(john)
movedog(david)
movefish(fabian) # 得到结果
# The bird named Bob is flying
# The bird named John is flying
# The dog named David is running
# The fish named Fabian is swimming

这样我们拿到一个变量bob,还要分清楚它表示“鸟”还是“狗”,然后在决定用movebird函数还是movedog函数。一般我们不会只实现move这一种功能,倘若有一二十种,四五十个函数堆在那里,得到的数据也有几十个,还要一一对应就很麻烦。除此之外,这些函数可能相互调用,如果想要将这个函数用到其他项目中,还要去找它调用了哪些函数一起复制到新项目中。如果这个代码很久没看了,再回来看更要花费很大的精力来对应。

而面向对象定义这个变量时就是通过类产生实例,要想知道用什么方法处理只要直接去找那个类的定义,甚至在编辑器的代码提示中就会直接列出来供你挑选。

所以面向对象无论在编程处理的过程,还是代码的管理上都有非常大的优势。

数据封装

当我们要定义很多函数要调用相同参数时,面向对象的使用会明显更方便,我们看下面一个例子

定义类如下

class Person:
'''细心的读者可能注意到这里的定义和前面形式不一样,没有object,在这里说明一下
python对象系统分为经典类和新式类,py2中不加object默认经典类,加了是新式类
py3中则默认不加也是新式类,所以以后我们定义时都不加object(虽然加也是一样的)''' def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height def description(self):
print("Description: name is %s, age is %s, height is %s"% (self.name,self.age,self.height)) def my(self):
print("My name is %s, and height is %s, and age is %s. "% (self.name,self.height,self.age))

调用实例

bob = Person("Bob",24,170)
mary = Person("Mary",10,160)
bob.description()
bob.my()
mary.description()
mary.my()
# Description: name is Bob, age is 24, height is 170
# My name is Bob, and height is 170, and age is 24.
# Description: name is Mary, age is 10, height is 160
# My name is Mary, and height is 160, and age is 10.

我们可以看到,创建对象时将数据传入,之后调用方法则不需要再赋值,每个方法在定义时就已将参数传入

上面的过程我们用面向过程来实现,下面提供两种实现方式,都有一定的弊端

第一种,最直接的传入参数

def description(name, age, height):
print("Description: name is %s, age is %s, height is %s"% (name,age,height)) def my(name, age, height):
print("My name is %s, and height is %s, and age is %s. "% (name,height,age)) description("Bob",24,170)
description("Mary",20,160)
my("Bob",24,170)
my("Mary",20,160)
# Description: name is Bob, age is 24, height is 170
# Description: name is Mary, age is 20, height is 160
# My name is Bob, and height is 170, and age is 24.
# My name is Mary, and height is 160, and age is 20.

上面这一种每次调用函数都要传入相同的参数,非常麻烦,如果参数更多,对象更多,则非常麻烦

第二种可以不用每次都传入

bob = dict(name='Bob',age=24,height=170)
mary = dict(name='Mary',age=20,height=160) def my(dict):
print("My name is %s, and height is %s, and age is %s. "% (dict['name'],dict['height'],dict['age'])) def description(dict):
print("Description: name is %s, age is %s, height is %s"% (dict['name'],dict['age'],dict['height'])) my(bob)
description(bob)
my(mary)
description(mary)
# My name is Bob, and height is 170, and age is 24.
# Description: name is Bob, age is 24, height is 170
# My name is Mary, and height is 160, and age is 20.
# Description: name is Mary, age is 20, height is 160

这种方法其实就是把参数变化一下,但是有一个很大的弊端,这两个函数的定义中,默认了传入的字典有name age height这三个键,如果没有就会发生各种错误。所以说这个函数的应用其实非常局限,几乎无法应用在其他地方,这种函数其实比较适合的是封装在一个类里面。

对象操作

有时候我们要实现一个对象的动态过程,即一个对象做了什么,再做什么,面向过程就很难实现了,看下面一个例子

class Individual:

    def __init__(self,full=10): # 默认值则创建实例时可以不用传入
self.full = full def fruit(self):
self.full += 1
return self # 使作用完这个方法后还可以调用其他方法 def meat(self):
self.full += 2
return self def run(self):
self.full -= 3
return self

上面定义了一个个体的类,他有一个属性可以理解成能量,他的方法有吃水果、吃肉、跑步,每一个方法调用后都会改变他的能量值。下面创建一个实例来调用

anyone = Individual()
anyone.full #
anyone.meat() # 让他吃一次肉
anyone.full #
anyone.run() # 让他跑一次
anyone.full #
anyone.meat().run() # 吃完再跑
anyone.full #

我们还可以进行区分,定义两个类,继承上面的类

class Boy(Individual):
'''定义男生一天吃肉跑,再吃再跑再吃'''
def oneday(self):
self.meat().meat().run().meat().fruit().run().meat()
print(self.full) class Girl(Individual):
'''定义女生每天吃的少但是不跑'''
def oneday(self):
self.meat().fruit()
print(self.full)

上面两个类定义了男生、女生一天的动作,通过调用oneday返回他们做一天后剩余的能量

bob = Boy()
bob.oneday() #
mary = Girl()
mary.oneday() #

上面这个过程,如果使用面向过程的方法,则需要对每个对象定义一个专门表示他们能量的变量,再定义函数对这些值进行修改,这会造成变量和参数的极大冗余,这里就不再举例子

用面向对象很多时候调用方法的时候都非常符合人的思维习惯,如上面的做完一件事再做一件事就用.连着写下去。这样的类还有很多,比如下面的字符串例子

  • 面向对象 a.casefold().join(['a','b']).strip().find('c')
  • 面向过程 find(strip(join(casefold(a),['a','b'])),'c')

假设上面的函数都是可用的,面向对象一步一步非常清晰:大小写转换-连接ab-去除两边空格-找到'c',而面向过程则层层嵌套,代码非常不易读,如果不嵌套则可能产生许多无用的中间变量(虽然R里面有管道操作可以解决这个问题,但是不是所有面向过程的语言都有实现这样的功能的)

总结面向对象编程的好处

  • 将对象进行分类,分别封装它们的数据和可以调用的方法,方便了函数、变量、数据的管理,方便方法的调用(减少重复参数等),尤其是在编写大型程序时更有帮助。
  • 用面向对象的编程可以把变量当成对象进行操作,让编程思路更加清晰简洁,而且减少了很多冗余变量的出现

参考

Python入门之面向对象编程(一)面向对象概念及优点的更多相关文章

  1. 面向对象编程(九)——面向对象三大特性之继承以及重写、Object类的介绍

    面向对象三大特性 面向对象三大特征:继承 :封装/隐藏 :多态(为了适应需求的多种变化,使代码变得更加通用!) 封装:主要实现了隐藏细节,对用户提供访问接口,无需关心方法的具体实现. 继承:很好的实现 ...

  2. 廖雪峰Java2面向对象编程-1面向对象-1面向对象基础

    1.对象的概念 面向对象编程:Object-Oriented Programming 对现实世界建立计算机模型的一种编程方法. 现实世界 计算机模型 Java代码 人 类/class class Pe ...

  3. python入门(五):面向对象

    面向对象术语 类(Class): 用来描述具有相同的属性和方法的对象的集合.它定义了该集合中每个对象所共有的属性和方法.对象是类的实例. 类变量:类变量在整个实例化的对象中是公用的.类变量定义在类中且 ...

  4. python入门(十三):面向对象(继承、重写、公有、私有)

    1. 三种类定义的写法  class P1:#定义类   加不加()都可以    pass   class P2():                    #带(),且括号中为空,类定义 pass ...

  5. Python入门 —— 03GUI界面编程

    GUI(Graphical User Interface) 即图形用户接口,又称图形用户接口. 是指采用图形方式显示的计算机操作用户界面.GUI 是屏幕产品的视觉体验和互动操作部分. "你的 ...

  6. 进击的Python【第六章】:Python的高级应用(三)面向对象编程

    Python的高级应用(三)面向对象编程 本章学习要点: 面向对象编程介绍 面向对象与面向过程编程的区别 为什么要用面向对象编程思想 面向对象的相关概念 一.面向对象编程介绍 面向对象程序设计(英语: ...

  7. 1.23 Python知识进阶 - 面向对象编程

    一.编程方法 1.函数式编程:"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论.它属于"结构化 ...

  8. Python 面向对象编程详解

    Python 的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言的一种继承.Py ...

  9. 『无为则无心』Python面向对象 — 45、面向对象编程

    目录 1.面向对象编程的概念 2.面向对象编程和面向过程编程的区别 (1)面向过程编程 (2)面向对象编程 3.举例理解面向对象 4.Python的面向对象编程 5.面向对象的几大核心特性 1.面向对 ...

随机推荐

  1. tesseract .net 中使用历程

    最近在看文字识别的实例,也查询很多文章,最后还是选定开源的引擎(tesseract3.0.1) 最开始找到的是用微软Office的一个组件实现的,个人感觉不是我想要的(要开源啊才是王道) http:/ ...

  2. poj1182 食物链【并查集-好题!】

    动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A.  现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种. 有人用两 ...

  3. FZU - 2150 Fire Game bfs+双起点枚举

    题意,10*10的地图,有若干块草地“#”,草地可以点燃,并在一秒后点燃相邻的草地.有墙壁‘·‘阻挡.初始可以从任意两点点火.问烧完最短的时间.若烧不完输出-1. 题解:由于100的数据量,直接暴力. ...

  4. 与虚拟机连接出现ora-12514错误解决方法

    谈到ora-12154问题,网上有一大堆解决方法,原因基本统一:tns或listener配置不正确.对于listener配置不正确的一般较少发生,大多数人都是按照默认配置一路“下一步”过来的,基本都是 ...

  5. web前端开发笔记(1)

     一.HTML标签书写有哪些规范? 页面编码. 文档声明. 关键字与描述. 行内元素不能包含块级元素. a标签不能嵌套a标签. 标签名和属性必须用小写字母书写,属性必须加引号,标签必须闭合,单标签页必 ...

  6. REDIS 六. 修复方案

    六. 修复方案 6.1 禁止一些高危命令(重启redis才能生效) 修改 redis.conf 文件,禁用远程修改 DB 文件地址 rename-command FLUSHALL "&quo ...

  7. iOS多线程编程之线程的状态(转载)

    一.简单介绍 线程的创建: self.thread=[[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil]; ...

  8. arcgis api for JavaScript _加载三维图层(scene layer)

    arcgis api for JavaScript _加载三维图层(scene layer) arcgis api for JavaScript  4.x 版本增加对三维的支持. 关于三维图层(sce ...

  9. Python 目录【持续更新中】

    Python 基础 字符编码 数据类型 文件处理 流程控制 练习题 函数,递归 匿名函数.内置函数 装饰器变形记 函数装饰器 递归 二分法 迭代器和生成器 协程函数 列表表达式 生成器表达式 异常处理 ...

  10. mysql 内置功能 事务 介绍

    事务用于将某些操作的多个SQL作为原子性操作,一旦有某一个出现错误,即可回滚到原来的状态,从而保证数据库数据完整性 创建数据库db12 create database db12 charset=utf ...