20201204-3 opp编程好处
面向对象编程(Object-Oriented Programming )介绍
对于编程语言的初学者来讲, OOP不是一个很容易理解的编程方式,大家虽然都按老师讲的都知道0OP的三大特性是
继承、封装、多态,并且大家也都知道了如何定义类、方法等面向对象的常用语法,但是一到真正写程序的时候,
还是很多人喜欢用函数式编程来写代码,特别是初学者,很容易陷入一个窘境就是
“我知道面向对象,我也会写类,但我依然没发现在使用了面向对象后,对我们的程序开发效率或其它方面带来什么好处,
因为我使用函数编程(指只用函数)就可以减少重复代码并做到程序可扩展了,为啥子还用面向对象?”。
对于此,我个人觉得原因应该还是因为你没有充分了解到面向对象能带来的好处,
今天我就写一篇关于面向对象的入门文章,希望能帮大家更好的理解和使用面向对象编程。
无论用什么形式来编程,我们都要明确记住以下原则:
1,写重复代码是非常不好的低级行为
2.你写的代码需要经常变更 开发正规的程序跟那种写个运行一次就扔了的小脚本一个很大不同就是,你的代码总是需要不断的更改,
不是修改 bug 就是添加新功能等,
所以为了日后方便程序的修改及扩展,你写的代码一定要遵循易读、易改的原则(专业术语叫可读性好、易扩展) 。 如果你把一段同样的代码复制、粘贴到了程序的多个地方以实现在程序的各个地方调用这个功能,
那日后你再对这个功能进行修改时,就需要把程序里多个地方都改一遍,
这种写程序的方式是有问题的,因为如果你不小心漏掉了一个地方没改,
那可能会导致整个程序的运行都出问题。
因此我们知道在开发中一定要努力避免写重复的代码,否则就相当于给自己再挖坑。 还好,函数的出现就能帮我们轻松的解决重复代码的问题,
对于需要重复调用的功能,只需要把它写成一个函数,
然后在程序的各个地方直接调用这个函数名就好了,
并且当需要修改这个功能时,只需改函数代码,然后整个程序就都更新了。
# 函数编程已经可以实现易改,易扩展的功能了
其实 OOP编程 的主要作用也是使你的代码修改和扩展变的更容易,那么小白要问了,
既然函数都能实现这个需求了,还要 OOP 干毛线用呢?
呵呵,说这话就像,古时候,人们打仗杀人都用刀,后来出来了枪,它的主要功能跟刀一样,也是杀人,
然后小白就问,既然刀能杀人了,那还要枪干毛线,
哈哈,显而易见,因为枪能更好更快更容易的杀人。
函数编程与 OOP 的主要区别就是 OOP 可以使程序更加容易扩展和易更改。 小白说,我读书少,你别骗我,口说无凭,证明一下,好吧,那我们就下面的例子证明给小白看。
相信大家都打过 CS 游戏吧,我们就自己开发一个简单版的 CS 来玩一玩。
# you are god, and now you wanna to create a dog
class Dog:
def bulk(self): # 自动出现了 self,先不用管 self 是什么
print("Chenronghua: wang wang wang!")
# 这样一个模子就完成了,只能叫,无法做别的事情 # 现在需要造对象了
d1 = Dog() # 一个狗就出来了
d2 = Dog() # 两个狗就出来了
d3 = Dog() # 三个狗就出来了 # 这样造狗的速度就很快了,不用重新写一遍 # 现在需要调用它的功能,让它叫,让三个狗同时都在叫
d1.bulk()
d2.bulk()
d3.bulk()
--->
Chenronghua: wang wang wang!
Chenronghua: wang wang wang!
Chenronghua: wang wang wang! # 结果是,都是 Chenronghua在叫,如何区分是不同的狗叫呢?
2
# 可以在造狗的时候起名字
class Dog:
def __init__(self,name):
self.name = name
# 以上def 暂时不需要了解原因,这个方法的目的只是为了传名字,__init__,现在就是用来传名字的
# self.name = name 是什么,暂时不用管
def bulk(self): # 自动出现了 self,先不用管 self 是什么
print("%s: wang wang wang!"% self.name) # 现在可以传名字,造对象了
d1 = Dog("陈荣华")
d2 = Dog("陈三炮")
d3 = Dog("陈老泡") d1.bulk()
d2.bulk()
d3.bulk()
--->
陈荣华: wang wang wang!
陈三炮: wang wang wang!
陈老泡: wang wang wang!
# 这时候执行,结果是一家三口都在叫
# 这样,就很容易的造三只狗
3
暂不考虑开发场地等复杂的东西,我们先从人物角色下手,
角色很简单,就俩个,恐怖份子、警察,他们除了角色不同,其它基本都一样,
每个人都有生命值、武器等。咱们先用非 OOP 的方式写出游戏的基本角色
#role 1
name = 'Alex
role = 'terrorist'
weapon = 'AK47'
life_value = 100 #role 2
name2= 'Jack'
role2 = 'police'
weapon2 = 'B22'
life_value2 = 100 上面定义了一个恐怖份子Alex和一个警察Jack,但只2个人不好玩呀,
一干就死了,没意思,那我们再分别一个恐怖分子和警察吧,
#role 1
name = 'Alex'
role = 'terrorist'
weapon = 'AK47'
life_value = 100
money = 10000 #role 2
name2 = "Jack"
role2 = 'police'
weapon2 = 'B22'
life_value2 = 100
money= 10000 #role 3
name3 = 'Rain'
role3 = 'terrorist'
weapon3 = 'C33'
life_value3 = 100
money3 = 10000 #rolw 4
name4 = 'Erict'
ro1e4 = 'police'
weapon4 = 'B511'
life_value4 = 100
money4 = 10006
4 个角色虽然创建好了,但是有个问题就是,每创建一个角色,我都要单独命名,
name1,name2,name3,name4..,
后面的调用的时候这个变量名你还都得记着,
要是再让多加几个角色,估计调用时就很容易弄混啦,
所以我们想一想,能否所有的角色的变量名都是一样的,但调用的时候又能区分开分别是谁? 当然可以,我们只需要把上面的变量改成字典的格式就可以啦。 roles ={
1:('name':'Alex',
'role:'terrorist',
'weapon':'AK47',
'life_value':100,
'money':15000,
},
2:{'name':'Jack',
'role':'police',
'weapon':'B22',
'life value':100,
'money':15000,
},
3:{'name':'Rain',
'role':'terrorist',
'weapon':'C33',
'life_value':100,
'money':1500,
},
4:{'name':'Eirc',
'role':'police'
'weapon':'B51',
'life_value': 100,
'money':15000,
print(roles[1]) # Alex
print(roles[2]) # Jack 很好,这个以后调用这些角色时只需要roles[1],roles[2]就可以啦,
角色的基本属性设计完了后,我们接下来为每个角色开发以下几个功能
1,被打中后就会掉血的功能
2.开枪功能
3,换子弹
4,买枪
5.跑、走、跳、下蹲等动作
6·保护人质(仅适用于警察)
7,不能杀同伴
8.。。。
我们可以把每个功能写成一个函数,类似如下 def shot (by_who):
#开了枪后要减子弹数
pass
def got_shot (who):
#中枪后要减血
who["1ife_value'] -= 10
pass
def buy_gun(who, gun_name):
#检查钱够不够,买了枪后要扣钱
pass
... so far so good,继续按照这个思路设计,再完善一下代码,游戏的简单版就出来了,
但是在往下走之前,我们来看看上面的这种代码写法有没有问题,
至少从上面的代码设计中,我看到以下几点缺陷:
1,每个角色定义的属性名称是一样的,但这种命名规则是我们自己约定的,
从程序上来讲,并没有进行属性合法性检测,
也就是说role 1 定义的代表武器的属性是weapon, role 2 ,3,4 也是一样的,
不过如果我在新增一个角色时不小心把weapon写成了wepon ,这个程序本身是检测不到的 2. terrorist 和 police 这2个角色有些功能是不同的,
比如police是不能杀人质的,但是terrorist可能,随着这个游戏开发的更复杂,
我们会发现这2个角色后续有更多的不同之处, 但现在的这种写法,我们是没办法把这2个角色适用的功能区分开来的,
也就是说,每个角色都可以直接调用任意功能,没有任何限制。 3. 我们在上面定义了got_shot()后要减血,也就是说减血这个动作是应该通过被击中这个事件来引起的,我们调用got_shot(),
got_shot ()这个函数再调用每个角色里的 life-value 变量来减血。但其实我不通过 got_shot(),
直接调用角色roles[role_id]['life-value']减血也可以呀,
但是如果这样调用的话,那可以就是简单粗暴啦,因为减血之前其它还应该判断此角色是否穿了防弹衣等,
如果穿了的话,伤害值肯定要减少, got_shot()函数里就做了这样的检测,你这里直接绕过的话,程序就乱了。
因此这里应该设计成除了通过got_shot(),其它的方式是没有办法给角色减血的,
不过在上面的程序设计里,是没有办法实现的。 4. 现在需要给所有角色添加一个可以穿防弹衣的功能,那很显然你得在每个角色里放一个属性来存储此角色是否穿了防弹衣,
那就要更改每个角色的代码,给添加一个新属性,这样太low了,不符合代码可复用的原则! 上面这4点问题如果不解决,以后肯定会引出更大的坑,有同学说了,解决也不复杂呀,
直接在每个功能调用时做一下角色判断啥就好了,没错,你要非得这么霸王硬上弓的搞也肯定是可以实现的,
那你自己就开发相应的代码来对上面提到的问题进行处理好啦。
但这些问题其实能通过 OOP 就可以很简单的解决。 之前的代码改成用 OOP 中的 “类” 来实现的话如下: class Role(object):
def __init__(self, name,role, weapon, life_value=100, money=15000):
self.name = name
self.role = role
self.weapon = weapon
self.lifevalue = life_value
self.money = money def shot(self):
print ("shooting...") def got_shot(self):
print("ah...I got shot...") def buy_gun(self,gun_name):
print ("just bought %s"%gun_name)
r1 = Role('Alex', 'police','AK47') #生成一个角色
r2 = Role('Jack', 'terrorist',' B22') #生成一个角色 先不考虑语法细节,相比函数式写法,上面用面向对象中的类来写最直接的改进有以下2点:
1.码量少了近一牛
2.角色和它所具有的功能可以一目了然看出来 # 类是一个角色,下面的每一个函数,相当于一个功能
接下来我们一起分解一下上面的代码分别是什么意思
1-1
class Role(object):
def __init__(self, name,role, weapon, life_value=100, money=15000):
self.name = name
self.role = role
self.weapon = weapon
self.lifevalue = life_value
self.money = money def shot(self):
print ("shooting...") def got_shot(self):
print("ah...I got shot...") def buy_gun(self,gun_name):
print ("%s just bought %s" % (self.name,gun_name)) r1 = Role('Alex', 'police','AK47') #生成一个角色
# 把一个类变成一个具体对象的过程,叫实例化
# 调用类,生成一个角色;叫做实例化(也可以称为 初始化一个类,造了一个对象)
# 造完对象后,就是一个具体的东西了,存在 r1 里
r2 = Role('Jack', 'terrorist',' B22') #生成一个角色 r1.buy_gun("AK47")
--->
Alex just bought AK47 # 整体理解类的定义,以及把类具体成人的过程 Role('Alex', 'police','AK47').got_shot()
Role('Alex', 'police','AK47')
# 这两句话,相当于造了两个不一样的人,内存是不一样的地址
# 所以,造完这个人后,以后如果想继续用,需要存入变量
1-1-1
r1.got_shot()
r1.buy_gun("b51")
# 存入变量后,就可以反复调用 1-1-2
# 实例化一个类,如果想传参数,只能通过 __init__ 方法
# 语法定义,只能如此
class Role(object):
def __init__(self, name,role, weapon, life_value=100, money=15000):
# __init__ 叫做 构造函数
# __init__ 作用是在实例化时,做一些类的初始化的工作
# 首先需要知道,实例化在内存中到底做了什么
self.name = name
self.role = role
self.weapon = weapon
self.lifevalue = life_value
self.money = money def shot(self):
print ("shooting...") def got_shot(self):
print("ah...I got shot...") def buy_gun(self,gun_name):
print ("%s just bought %s" % (self.name,gun_name))
2-1 实例化在内存中到底做了什么
class Role(object):
def __init__(self, name,role, weapon, life_value=100, money=15000):
self.name = name
self.role = role
self.weapon = weapon
self.lifevalue = life_value
self.money = money def shot(self):
print ("shooting...") def got_shot(self):
print("ah...I got shot...") def buy_gun(self,gun_name):
print ("%s just bought %s" % (self.name,gun_name)) print(Role)
--->
<class '__main__.Role'> # 说明 虽然看不到他的内存地址,但是它是存在的
# 虽然没有执行,但是本身是已经存在的 # 不管是否有 r1 内存都会生成
# 之前说,如果没有赋值变量名,就没了;因为用完后找不到了
# 认为只要没有变量名指向它,内存就可以销毁了
# 为了不让它被销毁,所以赋值变量名 r1;使他不会被销毁
# Role('Alex', 'police','AK47') 调用,就会立刻在内存中开辟一块空间,将 name ,role, weapon 等传给类
# 类中进行 self.name = name 等操作,数据是如何传进去的? # 类先为 调用的实例 开辟一块新的内存,传入 name = alex, role = Police 等;
# 这时内存已经存下来了,然后 Role('Alex', 'police','AK47') 告诉 r1 # 实例化时,直接将 r1 同时传进去 调用函数 ---> 执行 ---> 返回结果 # __init__ 叫做 构造函数
# __init__ 作用是在实例化时,做一些类的初始化的工作
# 首先需要知道,实例化在内存中到底做了什么
def __init__(self, name,role, weapon, life_value=100, money=15000):
self.name = name
self.role = role
self.weapon = weapon
self.lifevalue = life_value
self.money = money
# 是一个初始化的过程,本身也是一个函数,我们会默认为,调用函数 ---> 执行 ---> 返回结果
# 返回的结果,就是那个对象
# 中间的过程,self.name = name 等,就是开辟了内存,往里面存值,最后返回一个内存地址
# 于是变成了 r1 = Role.__init__() return x324342
# r1 调到内存地址,然后往里面存一些值 但其实,事实并不是这样,虽然可以这样实现;但并不是采取
r1 = Role.__init__() return x324342 这种方式
采取的是 Role(r1) 这种方式把 r1 本身当做 参数 传进去
Role(r1,"Alex","Police","15000")
将 r1 变量名传进去,然后往 r1 中存东西
r1.name = "Alex"
r1.role = "Police"
r1.money = 15000
是这样的实现方式,所以不需要返回值,就没有返回内存地址,因为在外面已经赋好了内存地址 就是 r1
这样赋值就 ok 了
是采取这样的方式 role 自己把 r1 传进去了,所以 前面还得有一个参数;所以 __init__ 都自动带一个 self
就是为了接收 r1 这个变量名
这个 self 就相当于 r1 class Role(object):
def __init__(self, name,role, weapon, life_value=100, money=15000):
self.name = name
self.role = role
self.weapon = weapon
self.lifevalue = life_value
self.money = money
# 这个是给每个实例的 # 下面这些是给类中共有的,是在类的内存中存着的
def shot(self):
print ("shooting...") def got_shot(self):
print("%s:ah...I got shot..."%self.name) # 本来是 r1.name 但 实际上是 self.name,因为是将 r1 传给self def buy_gun(self,gun_name):
print ("%s just bought %s" % (self.name,gun_name)) r1 = Role('Alex', 'police','AK47') #生成一个角色
r1.buy_gun()
# 其实是到类中调用,r1 中没有buy_gun()
# 所以其实是 Role.buy_gun()
# 内部就是转成了 Role.buy_gun(r1) r1 传进去
# 所以 buy_gun() 函数必须能够接收 r1,所以类中每写一个方法,就至少必须要有 一个 Self
# 这个 self 就是接收 r1,因为要知道是谁 买了抢
# self 就是谁调用这个类就是谁
3-1
class Role(object):
def __init__(self, name,role, weapon, life_value=100, money=15000):
self.name = name
self.role = role
self.weapon = weapon
self.lifevalue = life_value
self.money = money
# 这个是给每个实例的 # 下面这些是给类中共有的,是在类的内存中存着的
def shot(self):
print ("shooting...") def got_shot(self):
print("%s:ah...I got shot..."%self.name) def buy_gun(self,gun_name):
print ("%s just bought %s" % (self.name,gun_name)) r2 = Role('Jack', 'terrorist', 'B22')
r2.got_shot()
# 没有显示的写,但实际上转成了 Role.got_shot(r2)
# r2 在 got_shot 中就又变成了 self
--->
Jack:ah...I got shot... 所以,整个过程就是在初始化的时候,在初始化构造函数中做的事情就是开辟一块内存,把东西存进去,存到 r1 变量中
__init__ 下面的函数 永远还是在 Role类的内存中,通过 Role 实例化出来的实例如果想调用方法,就得到 Role 中去取,而不是在自己的实例中
20201204-3 opp编程好处的更多相关文章
- python【第六篇】面向对象编程
面向对象编程 一.编程范式:编程的方法论.程序员编程的“套路”及“特点”特点总结,抽象成方法.规范. 二.面向对象编程介绍: 1.描述 世界万物,皆可分类:世间万物,皆为对象:只要是对象,就肯定属于某 ...
- Android 面向协议编程 体会优雅编程之旅
Android中面向协议编程的深入浅出 http://blog.csdn.net/sk719887916/article/details skay编写 说起协议,现实生活中大家第一感觉会想到规则或者约 ...
- 《Node.js 高级编程》简介与第二章笔记
<Node.js 高级编程> 作者简介 Pedro Teixerra 高产,开源项目程序员 Node 社区活跃成员,Node公司的创始人之一. 10岁开始编程,Visual Basic.C ...
- flask 高级编程 上下文与栈(转)
转: https://www.cnblogs.com/wangmingtao/p/9372611.html 22.上下文与出入栈 22.1 请求过程 请求过来,flask会实例化一个Request C ...
- 快速入门函数式编程——以Javascript为例
函数式编程是在不改变状态和数据的情况下使用表达式和函数来编写程序的一种编程范式.通过遵守这种范式,我们能够编写更清晰易懂.更能抵御bug的代码.这是通过避免使用流控制语句(for.while.brea ...
- 【Python之路】第四篇--Python基础之函数
三元运算 三元运算(三目运算),是对简单的条件语句的缩写 # 书写格式 result = 值1 if 条件 else 值2 # 如果条件成立,那么将 “值1” 赋值给result变量,否则,将“值2” ...
- Common.Logging源码解析二
Common.Logging源码解析一分析了LogManager主入口的整个逻辑,其中第二步生成日志实例工厂类接口分析的很模糊,本随笔将会详细讲解整个日志实例工厂类接口的生成过程! (1).关于如何生 ...
- C#温故而知新系列 -- 闭包
闭包的由来 要说闭包的由来就不得不先说下函数式编程了.近几年函数式编程也是比较火热,我们先来看看函数式编程的一些基本的特性这个有助于我们理解闭包的由来. 函数式编程 函数式编程是一种编程模型,他将计算 ...
- Spring Day 2
**Spring框架的IOC之注解方式的快速入门** 步骤一:导入注解开发所有需要的jar包 步骤二:创建对应的包结构,编写Java的类:接口到实现类 步骤三:在src的目录下,创建applicati ...
随机推荐
- WPF控件库总结
前言 在使用WPF项目的时候, 一般首要的就是对UI部分的选型, 而WPF相关的UI控件和样式库在Githu也是非常多. 关于UI的部分,可以分为二种: 对控件本身没有很大的需求, 只需要在原有的基础 ...
- C语言设计模式(自我揣摩)
NBModule.h #ifndef _NBMODULEFRAME_H__ #define _NBMODULEFRAME_H__ #include "total.h" enum N ...
- MySql的远程登录问题
1.linux中先连接数据库:mysql -uroot -p(密码) 2.在mysql命令行中输入: GRANT ALL PRIVILEGES ON *.* TO '登录id'@'%' IDENTIF ...
- web自动化 下拉框、切换到新窗口
一.下拉框 相信大家在手动测试web页面时,遇到过下拉框吧,那进行web自动化测试时,如何操作下拉框,且看下文 1.selenium中提供了方法,先导入Select方法 from selenium.w ...
- zabbix地图显示全国延迟
Zabbix 地图显示全国延迟 1. 效果图 2. 实现方法 将地图.png上传到zabbix为背景,上传红绿点.png为图标.然后新建主机关联模板为ICMP Ping,新建一个拓扑图调用地图为背 ...
- [转载]Windows环境下 Hadoop Error: JAVA_HOME is incorrectly set. 问题
最近尝试在windows开发MR程序并且提交Job,在解压缩好hadoop,配置好环境变量后, 打开cmd 输入hadoop version 的时候出现以下错误: Error: JAVA_HOME i ...
- 一张图彻底理解Spring如何解决循环依赖!!
写在前面 最近,在看Spring源码,看到Spring解决循环依赖问题的源码时,不得不说,源码写的太烂了.像Spring这种顶级的项目源码,竟然存在着这种xxx的代码.看了几次都有点头大,相信很多小伙 ...
- Java基础教程——多态
直观地说,多态就是"一个对象,多种形态 ".比如观世音菩萨就有多种形态-- 每个人都有多种形态-- 具体地讲,多态是指"同一个对象.同一个方法(函数),表现出不同的行为& ...
- 新手上路之JDK8的下载、安装与PATH环境变量的配置
有些东西不常用总是会忘记,所以想把它写下来,方便以后自己想用的时候找得到:同时也进一步加深自己的记忆.接触JAVA的时间不长,言语或内容有不当之处,欢迎大佬们指正. 每一个学习JAVA的人都会经历的过 ...
- Java高薪训练营(对标阿里P7,限时分享)
某钩Java高薪训练营(部分,持续更新) 下载地址 防止网盘和谐多次补链修改,公众号回复「训练营」自提.