- 什么是面向对象和面向对象编程其它相关概念?
- 面向对象编程:面向对象编程是一种编程范式或编程风格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石
- 面向对象编程语言:是支持类或对象的语法机制,并有现成的语法机制,能方便地实现面向对象编程四大特性(封装、抽象、继承、多态)的编程语言。
- 面向对象分析:面向对象分析就是要搞清楚做什么
- 面向对象设计:搞清楚怎么做,这两个阶段的最终产出就是类的设计,包括程序被拆解为哪些类、每个类有哪些方法,类与类之间如何交互等等。
- 面向对象的四大特性
- 封装:封装也叫做信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据
- 举例:
"""面向对象封装例子"""
import uuid
from time import time
class Wallet(object):
def __init__(self):
self._id = uuid.uuid4()
self._create_time = time()
self._balance = 0
self._balance_modified_time = time()
self.test = 'xxx'
def get_wallet_id(self):
return self._id
def get_wallet_create_time(self):
return self._create_time
def get_balance_modified_time(self):
return self._balance_modified_time
def increase_balance(self, increase_balance):
self._balance += increase_balance
def decrease_balance(self, decrease_balance):
self._balance -= decrease_balance
if __name__ == '__main__':
w = Wallet()
print(w.test)
print(w._id)
-
例子说明
- 从代码的角度来看,我们可以发现,Wallet类主要有四个属性(也可以叫做成员变量),就是我们前面定义的信息或者数据。其中_id表示钱包的唯一编号,create_time表示钱包的创建时间,balance表示钱包中的余额,balance_modified_time表示上次钱包余额变更的时间。我们运用封装的特性,对钱包的四个属性的访问方式进行了限制。调用者只允许通过上面这5个方法来访问钱包中的属性或者修改
- get_wallet_id()
- get_wallet_create_time()
- get_balance_modified_time()
- increase_balance()
- decreace_balance()
- 这样设计是目的:从业务的角度来说,id、create_time是创建钱包的时候就确定好了,之后不应该再改动,所以,我们并没有在Wallet类中,暴露id、create_time这两个属性的任何修改方法,比如说set方法。而且,这两个属性的初始化设置,对于wallet类的调用者来说也是透明的,因此是内部初始化的,不需要传进来的。对于钱包余额balance这个属性来说,从业务的角度来说,只能增或者减,不会被重新设置。所以在wallet类中,只暴露了increase_balance()和decrease_balance()方法,并没有暴露set方法。对于balance_modified_time这个属性的修改操作完全封装在了increase_balance()和decrease_balance这两个方法中,不对外暴露任何修改这个属性的方法也细节。
- 所以总的来说,封装主要是为了隐藏数据或者数据访问保护,对于封装这个特性,我们需要编程语言本身提供一定的语法机制来支持。这个语法机制就是访问权限控制。比如说private、public等关键字就是java语言中的访问权限控制语法。python就是通过单下划线或者双下划线来定义私有
- 封装的意义和解决哪些编程问题
-
如果对类中的属性的访问不做任何限制,那任何代码都可以访问、修改类中的属性,这样是不可控制的,属性可以被随意以各种奇葩的方式修改,而且修改逻辑可能散落在代码中的各个角落,势必影响代码的可读性和可维护性。比如说某个同事可以偷偷修改wallet中的余额。另外一方面,类通过有限的方法暴露必要的操作,也能提高类的易用性。如果所有方法都暴露了,那么调用者必须熟悉业务才行。相反如果隐藏了,调用者不必知道背后太多的业务细节,用错的概率会少很多
-
-
抽象:封装主要讲的是如何隐藏信息、保护数据、而抽象讲的是如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。
-
实际上,类的方法是通过编程语言中的“函数”这一语法机制来实现的。通过函数包裹具体的实现逻辑,这本身就是一种抽象。也就是说,我们其实定义的函数就是一种抽象的特性了。调用者在使用函数的时候,并不需要去研究函数内部的具体实现逻辑,只需要通过函数的命名、注释或者文档,了解其提供了什么功能,就可以额直接使用了
-
抽象的意义和解决哪些编程问题?
-
抽象和封装其实都是人类处理复杂性的手段。面对复杂的系统的时候,人脑能承受的信息复杂程度有限,所以我们需要忽略掉非关键性的实现细节。而抽象作为一种只关注功能点不关注实现的设计思路,正好符合这种逻辑。抽象在代码设计中,起到非常重要的指导作用。很多设计原则都体现了抽象的思想,比如说基于接口而非实现编程,开放封闭。换一个角度,在命名类的方法的时候,也要有抽象思维,不要再方法名定义中暴露太多细节,保证在某个时间点需要改变方法的实现逻辑的时候,不用去修改其定义。比如说,get_aliyun_picture,就不是一个具有抽象思维命名,应该是一个get_picture()
-
-
继承:继承用来表示类之间的is-a关系,比如说猫是一种哺乳动物。从继承关系上来讲,继承可以分成两种模式,单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类,比如说猫既是哺乳动物又是爬行动物
-
继承的意义和解决哪些编程问题?
-
继承的最大的好处就是代码复用。假如两个类有一些相同的属性和方法,就可以将这些相同的部分抽取到父类中,让两个子类继承父类。这样子,两个子类就可以重用父类中的代码,避免代码重复写多遍。不过继承会有一个过度使用继承的问题,也就是说继承层次过深过于复杂,就会导致代码可读性、可维护性变差。导致为了查看一个功能,需要不停的深入查看父类代码
-
-
多态:多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。多态这种特性也是需要编程语言提供特殊的语法机制来实现的,而python崇尚的是鸭子模型,也就是只要两者都有相同的方法,就可以实现多态,并不需要要求它们有什么关联。
-
举例子:
-
利用鸭子类型 duck-typing来实现多态特性,如下面的例子所示,鸭子类型实现多态的方式非常灵活,虽然Logger和DB两个类没有任何关系,既不是继承关系,也不是接口跟实现关系,但是他们都定义了record()方法,就可以被传递到test(recorder)方法中,在实际运行的时候,执行对应的record(),也就是说,两个类只要具有相同的方法,就可以实现多态,并不要求两个类之间有任何关系,这就是duck-typing。
class Logger:
def record(self):
print("I write a log into file.")
class DB:
def record(self):
print("I insert data into db. ")
def test(recorder):
recorder.record()
def demo():
logger = Logger()
db = DB()
test(logger)
test(db)
- 多态实现的意义和解决的编程问题是什么?
- 多态能够提高代码的可扩展性和复用性。就像上面的test(recorder)方法实现的是数据记录的功能,对于新添加的类,比如说Logger和DB,我们只要实现了record()方法,那么就能够传入到test(recorder)中进行实现数据记录的功能,不管有多少地方和类需要实现record方法,只需要新增加一个类,然后实现自己的record方法就行了,原来的test(recorder)方法甚至不需要改动,因此这样代码的可扩展性和复用性就很高。多态也是很多设计模式、设计原则的代码实现基础。