07 面向对象介绍
写代码的思想转变
面向过程
从名字可以看出它是注重过程的。当解决一个问题的时候,面向过程会把事情拆分成: 一个个函数和数据(用于方法的参数) 。然后按照一定的顺序,执行完这些方法(每个方法看作一个过程),等方法执行完了,事情就搞定了。
面向对象
当解决一个问题的时候,面向对象会把事物抽象成对象的概念,就是说这个问题里面有哪些对象,然后给对象赋一些属性和方法,然后让每个对象去执行自己的方法,问题得到解决。
区别
例子一
问题: 洗衣机里面放有脏衣服,怎么洗干净?
面向过程的解决方法:
- 执行加洗衣粉方法
- 执行加水方法
- 执行洗衣服方法
- 执行清洗方法
- 执行烘干方法
以上就是将解决这个问题的过程拆成一个个方法(是没有对象去调用的),通过方法的执行来解决问题。
面向对象的解决方法:
-
我先弄出两个对象:“洗衣机”对象和“人”对象
-
针对对象“洗衣机”加入一些属性和方法:“洗衣服方法”“清洗方法”、“烘干方法”
-
针对对象“人”加入属性和方法:“加洗衣粉方法”、“加水方法”
-
然后执行
人.加洗衣粉
人.加水
洗衣机.洗衣服
洗衣机.清洗
洗衣机.烘干
解决同一个问题,面向对象编程就是先抽象出对象,然后用对象执行方法的方式解决问题。
例子二 打麻将例子 你 我 他
面向过程: 打麻将 (你,我,他)
---------解决问题 拆成一个动作,把数据丢进去
面向对象: 我.打麻将(你,他) or 你.打麻将(我,他) or 他.打麻将(我,你)
---------解决问题 抽象出对象,对象执行动作
优缺点
用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。
蛋炒饭最后的一道工序肯定是把米饭和鸡蛋混在一起炒匀。盖浇饭呢,则是把米饭和盖菜分别做好,你如果要一份红烧肉盖饭呢,就给你浇一份红烧肉;如果要一份青椒土豆盖浇饭,就给浇一份青椒土豆丝。
蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。
到底是蛋炒饭好还是盖浇饭好呢?其实这类问题都很难回答,非要比个上下高低的话,就必须设定一个场景,否则只能说是各有所长。如果大家都不是美食家,没那么多讲究,那么从饭馆角度来讲的话,做盖浇饭显然比蛋炒饭更有优势,他可以组合出来任意多的组合,而且不会浪费。
盖浇饭的好处就是菜饭分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是”可维护性“比较好,”饭” 和”菜”的耦合度比较低。蛋炒饭将”蛋”“饭”搅和在一起,想换”蛋”“饭”中任何一种都很困难,耦合度很高,以至于”可维护性”比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。
概念
封装
封装指的是将数据(属性)和操作这些数据的方法(功能)封装在一起,并通过访问权限控制(如公有、私有)对外隐藏细节,只暴露必要的部分供外部使用。这样可以保护数据,避免被意外修改,同时让代码更易维护。
手机上的按键和屏幕是对用户公开的接口,但手机内部的电路和芯片对用户是隐藏的。用户不需要知道手机内部是如何工作的,只需使用提供的功能(打电话、发信息)。
class BankAccount:
def __init__(self, account_holder, initial_balance):
self.account_holder = account_holder # 公开属性
self.__balance = initial_balance # 私有属性
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"存款成功!余额:{self.__balance}")
else:
print("存款金额必须大于0。")
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
print(f"取款成功!余额:{self.__balance}")
else:
print("余额不足或金额无效。")
def get_balance(self):
return self.__balance # 提供访问私有属性的方法
# 使用示例
account = BankAccount("张三", 1000)
account.deposit(500)
account.withdraw(200)
print("余额是:", account.get_balance())
- 私有属性
__balance只能通过类内部的方法访问。 - 公开方法
deposit和withdraw是控制操作的唯一入口。
继承
继承是指一个类可以“继承”另一个类的属性和方法,使得代码的复用性和扩展性更强。被继承的类称为“父类”或“基类”,继承的类称为“子类”或“派生类”。子类可以直接使用父类的功能,也可以通过覆盖或扩展来实现自己的特性。
鸟是一个大类(基类),各种具体的鸟(如麻雀、鸽子)是其子类。所有鸟都有“翅膀”和“飞行”的能力(基类的属性和方法),但麻雀和鸽子也有各自的特殊性(子类的扩展功能)。
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name}正在吃东西。")
# 子类继承父类
class Dog(Animal):
def bark(self):
print(f"{self.name}正在汪汪叫!")
class Cat(Animal):
def meow(self):
print(f"{self.name}正在喵喵叫!")
# 使用示例
dog = Dog("小黑")
dog.eat() # 调用父类的方法
dog.bark() # 调用子类的方法
cat = Cat("小白")
cat.eat()
cat.meow()
- 子类继承了父类的
eat方法。 - 子类增加了自己的功能(
bark和meow)。
多态
多态是指同一个方法在不同的对象中有不同的实现,表现出多种形式。它可以通过继承和方法重写来实现,允许我们以统一的方式操作不同类型的对象。
不同品牌的遥控器(如电视遥控器、空调遥控器)看起来很相似(外部接口相同),但它们的按键操作效果不同(内部逻辑不同)。
class Animal:
def make_sound(self):
pass # 定义一个通用方法接口
class Dog(Animal):
def make_sound(self):
print("汪汪汪!")
class Cat(Animal):
def make_sound(self):
print("喵喵喵!")
# 统一调用接口
def animal_sound(animal):
animal.make_sound()
# 使用示例
dog = Dog()
cat = Cat()
animal_sound(dog) # 输出:汪汪汪!
animal_sound(cat) # 输出:喵喵喵!
- 父类
Animal定义了一个通用接口make_sound。 - 不同的子类实现了自己的
make_sound方法。 - 可以用统一的方式
animal_sound调用不同对象的行为。
总结表格
| 特性 | 定义 | 现实例子 | 代码关键点 |
|---|---|---|---|
| 封装 | 将数据和方法封装在类中,通过权限控制隐藏不必要的细节 | 手机内部电路对用户隐藏,只暴露打电话功能 | 使用私有属性(__ 开头)和公开方法来控制数据访问 |
| 继承 | 子类可以继承父类的属性和方法,并可以扩展或覆盖父类的方法 | 鸟类是基类,麻雀和鸽子是子类,有共有和各自特有的功能 | 子类通过 class 子类(父类): 语法继承父类,父类方法可直接使用,必要时可以覆盖或扩展 |
| 多态 | 同一方法在不同对象中有不同实现,通过统一接口调用不同对象的功能 | 不同品牌的遥控器按键外观相同,但功能不同 | 父类提供统一接口,子类重写方法,通过统一函数操作不同的子类 |
练习
定义一个类描述数字时钟。
from time import sleep
class Clock(object):
"""数字时钟"""
def __init__(self, hour=0, minute=0, second=0):
"""初始化方法
:param hour: 时
:param minute: 分
:param second: 秒
"""
self._hour = hour
self._minute = minute
self._second = second
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
"""显示时间"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
clock = Clock(23, 59, 58)
while True:
print(clock.show())
sleep(1)
clock.run()
定义一个类描述平面上的点并提供移动点和计算到另一个点距离的方法。
from math import sqrt
class Point(object):
def __init__(self, x=0, y=0):
"""初始化方法
:param x: 横坐标
:param y: 纵坐标
"""
self.x = x
self.y = y
def move_to(self, x, y):
"""移动到指定位置
:param x: 新的横坐标
"param y: 新的纵坐标
"""
self.x = x
self.y = y
def move_by(self, dx, dy):
"""移动指定的增量
:param dx: 横坐标的增量
"param dy: 纵坐标的增量
"""
self.x += dx
self.y += dy
def distance_to(self, other):
"""计算与另一个点的距离
:param other: 另一个点
"""
dx = self.x - other.x
dy = self.y - other.y
return sqrt(dx ** 2 + dy ** 2)
def __str__(self):
return '(%s, %s)' % (str(self.x), str(self.y))
p1 = Point(3, 5)
p2 = Point()
print(p1)
print(p2)
p2.move_by(-1, 2)
print(p2)
print(p1.distance_to(p2))