类和面向对象、封装、继承、多态
类和面向对象中的三大特性:封装、继承、多态
面向对象中的三大特性:封装、继承、多态
封装
封装是面向对象的三大特性之一,封装的目的主要是为了保证数据的安全性,假设有一个"圆形"的类Circle,它有半径radius这个属性
python
class Circle:
def __init__(self, radius):
self.radius = radius
然后创建一个圆对象Circle
python
circle = Circle(5) #创建一个对象,半径为5
但是这样,外部代码可以直接访问和修改半径,甚至将其设置为负数,这样的设计显然是不合理的。
python
circle.radius = -10
为了防止这样的问题发生,可以通过封装隐藏对象中一些不希望被外部所访问到的属性或方法,为以下两步:
- 将对象的属性名,修改为一个外部不知道的名字
- 提供
getter
和setter
来获取和设置对象的属性
一般情况下,对于对象的隐藏属性,使用双下划线开头:
__xxx
python
class Circle:
def __init__(self, radius):
#1.将属性名,改为外部不知道的一个名字
self.__radius = radius #使用双下划线前缀将属性私有化
#2.定义get方法,获取属性
def get_radius(self):
return self.__radius
#定义set方法,设置属性
def set_radius(self, radius):
self.__radius = radius
circle = Circle(5) #创建一个新对象,半径为5
#只有通过对应的方法,才能修改属性
circle.set_radius(10)
使用封装,我们隐藏了类的一些属性,具体的做法是使用getter
方法获取属性,使用setter
方法设置属性,如果希望属性是只读的,则可以直接去掉setter方法,如果希望属性不能被外部访问,则可以直接去掉getter方法。
继承
在对象中,总有一些操作是重复的,比如说Person
类具有姓名、身高、年龄等特征,并具有一些行走、吃饭、睡觉的方法,而我们要实现一个Teacher
类,Teacher
首先也是一个人,他也基本人的特征和方法,那我们是不是也应该用代码去实现这些特征和方法呢,这就势必会产生一些重复的代码。
可以采用"继承"的方式使得一个类获取到其他类中的属性和方法。在定义类时,可以在类名后的括号指定当前类的父类(超类), 子类可以直接继承父类中的所有属性和方法,从而避免编写重复性的代码,此外我们还可以对子类进行扩展。
假设,有一个图形类shape,它具有两个属性和一个方法,属性为颜色和类型,方法为求图形的的面积。
python
class Shape:
# 包含颜色和类型两个属性
def __init__(self, shape_type, color):
self.type = shape_type
self.color = color
#计算图形面积的方法
def calculate_area(self):
#pass表示空语句,不需要执行任何操作
pass
shape = shape('shape','white')
还需要一个圆的类,继承自shape类
python
class Circle(shape);
def __init__(self, shape_type, color, radius):
super().__init__(shape_type, color)
self.radius = radius
#计算圆的面积
def calculate_area(self):
return 3.14 * self.radius * self.radius
circle = Circle('circle', 'white', 10)
#计算圆的面积
circle.calculate_area()
在上面的示例代码中,图形类拥有两个属性和一个方法,圆的类在图形类的基础上添加了半径这个属性。
父类和子类中含有一些共同属性,在重写子类时,为了省略重复的代码,可以通过super()
动态的获取当前类的父类, 并调用父类的__init__()
方法从而初始化父类中定义的属性。
在子类和父类中都有calculate_area
这个方法,这被称为方法的重写,子类会优先调用自己的方法而不是父类的方法。如果子类的对象调用一个方法,发现并没有提供这个方法,就会从当前对象的父类中寻找,如果父类中有则直接调用父类中的方法,如果还没有,就从父类的父类中寻找,就好像,当父亲和儿子都拥有一样东西,会优先使用自己的,如果发现自己没有,才会使用继承的方法。
多态
多态常常和继承紧密联系,它允许不同的对象对方法调用做出不同的响应。你可以使用基类定义通用的代码,然后在派生类中提供提供特定的实现,从而在调用方法时调用不同的方法:
python
class Shape:
#基类的计算面积的方法:
def calculate_area(self):
pass
class Ciecle(Shape):
def __init__(self, radius):
self.radius = radius
#Circle类的计算面积的方法
def calculate_area(self):
return 3.14 * self.radius * self.radius
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
#Rectangle类的计算面积的方法
def calculate_area(self):
return self.width * self.height
#创建不同类型的图形对象
circle = Circle(2)
rectangle = Rectangle(4, 3)
#存放图形对象的列表
shapes = [circle, rectangle]
#计算面积
for shape in shapes:
#列表中的每个元素都调用计算面积的方法
area = shape.calculate_area()
#输出面积
print(f"Area: {area:.2f}")
上面的代码中,基类Shape实现了calculate_area方法,两个派生类Circle和Rectangle则是重写了calculate_area方法,它们有着不同的计算逻辑。之后我们创建了一个包含不同类型的图形对象的列表shapes,然后循环遍历该列表并调用calculate_area方法,景观调用的方法时根据对象的类型动态确定的,这其实就是多态的概念。
代码如下:
python
#shape类
class Shape:
def __init__(self, shape_type):
self.type = shape_type
def caculate_area(self):
pass
# Circle类,包含 radius, 计算面积的方法
class Circle(Shape):
def __init__(self, radius):
super().__init__("Circle")
self.radius = radius
def caculate_area(self):
return 3.14 * self.radius * self.radius
# Rectangle类,包含 width 和 height, 计算面积的方法
class Rectangle(Shape):
def __init__(self, width, height):
super().__init__("Rectangle")
self.width = width
self.height = height
def caculate_area(self):
return self.width * self.height
shapes = []
while True:
s = input().split()
if s[0] == "end":
break
if s[0] == "rectangle":
width, height = map(int, s[1: 3])
# width, height = int(s[1]), int(s[2])
shapes.append(Rectangle(width, height))
elif s[0] == "circle":
radius = int(s[1])
shapes.append(Circle(radius))
for shape in shapes:
print(f"{shape.type} area: {shape.caculate_area():.2f}")