面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式,它使用“对象”来设计软件。面向对象编程的主要特性包括封装、继承、多态性和抽象。这些特性使得OOP特别适合处理大型、复杂的软件系统。
特性
1. 封装(Encapsulation)
封装是OOP的核心特性之一,它指的是将对象的数据(属性)和操作这些数据的方法绑定在一起,形成一个紧密的单元。封装的目的是隐藏对象的内部细节,只暴露必要的操作接口给外界。这样做的好处是减少了外部对对象内部的直接访问,降低了代码的耦合度,提高了数据的安全性和代码的可维护性。
class Person:def __init__(self, name, age):self._name = name # 使用下划线(_)标识属性为受保护的self._age = agedef get_name(self):return self._namedef set_name(self, name):self._name = namedef get_age(self):return self._agedef set_age(self, age):if age >= 0:self._age = ageelse:print("Age cannot be negative.")# 创建一个Person对象
person1 = Person("Alice", 30)# 通过公共方法访问和修改属性
print(person1.get_name()) # 输出: Alice
print(person1.get_age()) # 输出: 30person1.set_name("Bob")
person1.set_age(25)print(person1.get_name()) # 输出: Bob
print(person1.get_age()) # 输出: 25# 尝试修改年龄为负数,将触发封装的保护机制
person1.set_age(-10) # 输出: Age cannot be negative.
print(person1.get_age()) # 输出: 25# 尝试直接访问和修改属性,还是可以直接访问和修改,只是不建议这样做,pycharm也会提示
person1._name = 123
print(person1._name) # 输出: 123
在Python中,尽管使用下划线(_)标识属性为受保护的(protected),但这只是一种命名约定,实际上Python并没有严格的访问控制机制。因此,在上面的例子中仍然可以通过person1._name = 123
这样的方式来直接修改对象的属性。
尽管Python中的属性命名约定可以提示其他程序员某些属性是受保护的,但它并不会阻止直接访问和修改这些属性。如果希望实现真正的封装和访问控制,可以考虑使用属性装饰器(property decorator)来定义属性的getter和setter方法,从而实现更严格的属性访问控制。
class Person:def __init__(self, name, age):self._name = nameself._age = age@propertydef name(self):return self._name@name.setterdef name(self, value):self._name = value@propertydef age(self):return self._age@age.setterdef age(self, value):if value >= 0:self._age = valueelse:print("Age cannot be negative.")# 创建一个Person对象
person1 = Person("Alice", 30)# 通过属性访问器访问和修改属性
print(person1.name) # 输出: Alice
print(person1.age) # 输出: 30person1.name = "Bob"
person1.age = 25print(person1.name) # 输出: Bob
print(person1.age) # 输出: 25person1.age = -10 # 输出: Age cannot be negative.
print(person1.age) # 输出: 25
2. 继承(Inheritance)
继承是一种创建新类的方式,新创建的类称为子类,它继承了另一个类(称为父类)的属性和方法。继承支持代码复用,使得子类可以扩展或修改父类的行为。
在Python中,继承可以是单继承也可以是多继承。
2.1 单继承
# 定义一个父类
class Animal:def __init__(self, breed):self.breed = breeddef speak(self):print(f"{self.breed} 发出声音。。。")# 定义一个子类,继承自Animal
class Dog(Animal):def __init__(self, breed, name):super().__init__(breed) # 调用父类的初始化方法self.name = namedef speak(self):super().speak()print(f"{self.name}在叫。。。")# 创建一个Animal对象
animal = Animal("普通动物")
animal.speak()# 创建一个Dog对象
dog = Dog("泰迪", "黄毛")
dog.speak()
2.2 多继承
# 定义一个父类
class Animal:def __init__(self, name):self.name = nameprint("【初始化动物类】")def speak(self):print(f"{self.name} 发出声音。。。")# 定义另一个父类
class Flyable:def __init__(self):print("【初始化飞行类】")def fly(self):print("飞在高空中。。。")# 定义一个子类,继承自Animal和Flyable
class Bird(Animal, Flyable):def __init__(self, name, species):super().__init__(name) # 调用Animal类的初始化方法self.species = speciesprint("【初始化鸟类】")def speak(self):print(f"{self.species} {self.name} 啾啾叫。。。")# 创建一个Bird对象
bird = Bird("小七", "麻雀")
bird.speak()
bird.fly()print(Bird.__mro__)
2.3 MRO
在以上多继承的例子中,Bird类的构造器中super调用,怎么知道是Animal类而不是Flyable类的???
答:在Python中,当使用super()
函数时,它遵循方法解析顺序(Method Resolution Order, MRO)来决定如何从多个基类中继承属性和方法。
MRO(Method Resolution Order)是一种规则,用于确定Python中类的方法调用顺序。当在面向对象编程中使用继承时,尤其是在多重继承的情况下,可能会出现一个方法在多个父类中都存在的情况。为了解决这种潜在的歧义,Python使用MRO来明确地确定应该首先从哪个父类中寻找该方法。
MRO背后的算法最初基于C3线性化算法。这个算法有几个关键目标:
- 子类总是优先于父类
- 如果一个类继承自多个父类,则根据它们在定义中出现的顺序排列
- 每个父类保证按照其声明时相同的顺序被检查
从Python 2.3开始,默认情况下所有新式类(即显式或隐式地继承自object
)都使用C3线性化来计算MRO。可以通过两种方式查看任何给定类的MRO:
- 使用内置函数
help()
。 - 查看该类的
__mro__
属性。
Bird
类继承自 Animal
和 Flyable
类。根据定义的继承顺序(即首先是 Animal
, 然后是 Flyable
)
print(Bird.__mro__)
输出:
(<class '__main__.Bird'>, <class '__main__.Animal'>, <class '__main__.Flyable'>, <class 'object'>)
这意味着当调用 super().__init__(name)
时,Python会按照MRO列表查找下一个具有 __init__
方法的类。因此,在这种情况下,它首先找到并调用了 Animal.__init__
方法而不是 Flyable.__init__
3. 多态性(Polymorphism)
多态性是指不同类的对象对同一消息作出响应的能力,即同一个接口可以被不同的对象以不同的方式实现。这意味着编写的代码可以对不同类型的对象进行操作,而不需要知道具体的类是什么,只需要知道对象支持的接口即可。多态性提高了程序的灵活性和可扩展性。
# 定义一个父类
class Animal:def speak(self):pass# 定义两个子类,分别实现了speak方法
class Dog(Animal):def speak(self):return "Woof!"class Cat(Animal):def speak(self):return "Meow!"# 定义一个函数,接受Animal对象作为参数,并调用其speak方法
def make_sound(animal):return animal.speak()# 创建一个Dog对象和一个Cat对象
dog = Dog()
cat = Cat()# 调用make_sound函数,分别传入Dog对象和Cat对象
print("Dog对象的调用", make_sound(dog)) # 输出: Woof!
print("Cat对象的调用", make_sound(cat)) # 输出: Meow!
4. 抽象(Abstraction)
抽象是将复杂的现实问题简化的过程,它通过创建简化的模型来表示复杂的实体,这个模型只包含对于当前问题重要的信息。在面向对象编程中,抽象通常是通过定义类来实现的,类提供了一种方式来定义抽象数据类型,通过隐藏所有的实现细节,只暴露出有限的接口与外界交互。
抽象类是一种不能被实例化的类,它通常用作其他类的基类,定义了一些抽象方法,子类需要实现这些抽象方法。Python通过抽象基类(Abstract Base Classes, ABCs)提供了对抽象的支持。使用abc
模块可以创建抽象基类。
from abc import ABC, abstractmethod# 定义一个抽象基类 形状,如果一个类继承自ABC类,那么这个类就是一个抽象基类。
class Shape(ABC):# 通过注解将area和perimeter方法标记为抽象方法,子类必须实现这些方法。@abstractmethoddef area(self):pass@abstractmethoddef perimeter(self):pass# 实现具体子类 圆形
class Circle(Shape):def __init__(self, radius):self.radius = radius# 实现area方法def area(self):return 3.14 * self.radius ** 2# 实现perimeter方法def perimeter(self):return 2 * 3.14 * self.radius# 实现具体子类 矩形
class Rectangle(Shape):def __init__(self, length, width):self.length = lengthself.width = width# 实现area方法def area(self):return self.length * self.width# 实现perimeter方法def perimeter(self):return 2 * (self.length + self.width)# 尝试创建Shape实例将引发错误,因为它是一个抽象基类。TypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter
# shape = Shape()# 创建Circle和Rectangle对象并调用它们的方法。计算圆形和矩形的面积和周长。
circle = Circle(radius=5)
print(f"Circle Area: {circle.area()}")
print(f"Circle Perimeter: {circle.perimeter()}")rectangle = Rectangle(length=4, width=6)
print(f"Rectangle Area: {rectangle.area()}")
print(f"Rectangle Perimeter: {rectangle.perimeter()}")
在这个示例中:
Shape
类通过继承自ABC
并标记area()
和perimeter()
方法为抽象方法(使用装饰器@abstractmethod
),成为了一个抽象基类。这意味着你不能直接实例化Shape
, 而必须先定义其子类,并且在子类中实现所有标记为抽象的方法。- 我们定义了两个具体的形状:圆形 (
Circle
) 和矩形 (Rectangle
) ,作为Shape
的子类型,并分别实现了计算面积和周长的逻辑。 - 最后部分展示了如何创建具体形状对象并调用它们各自计算面积和周长的功能。
通过这种方式,我们利用了面向对象编程中“接口”或“契约”的概念:即一组预期行为(在本例中即计算面积和周长),而不需要关心这些行为背后具体如何实施数学逻辑。
对象的关系
在面向对象编程中,对象之间的关系主要可以分为以下几种类型:
- 继承(Inheritance):表示一个类(子类)继承另一个类(父类)的属性和方法。这是一种“是一个”(is-a)关系,例如,狗是一种动物,因此狗类可以继承自动物类。
- 组合(Composition):表示一个对象包含另一个对象或多个对象的实例作为其成员变量。这表明了一种“有一个”(has-a)关系。例如,汽车有引擎,所以汽车类可以包含引擎类的实例作为其成员。
- 聚合(Aggregation):也是一种特殊形式的组合,但它允许被包含对象与容器对象之间保持相对独立;即容器对象被销毁时,并不意味着被包含的对象也会被销毁。例如,部门和员工之间就是聚合关系;部门解散并不意味着里面的员工也消失了。
- 依赖(Dependency):指一个类使用到另外一个类。这通常体现在某个类的方法通过参数、返回值或局部变量使用到另外一个类型。这是一种较弱的关联方式,“使用了”(uses-a) 关系。
- 关联(Association):表示两个或多个对象之间有联系但彼此相对独立。它们可能会互相交流信息而无需彼此了解对方更多信息。“知道”(knows-a) 关系描述了两个及以上完全独立构造体之间通过消息传递进行通信而建立起来的链接。
- 实现(Implementation):特指接口与实现该接口的具体类之间的关系,在某些语言中如Java中非常明显。“能做”(can-do) 关系描述了类型如何通过接口声明自己能够执行哪些操作。
通俗点讲:
人类继承(Inheritance)自动物类,人的各种器官类组合(Composition)成了人类,很多人聚合(Aggregation)成一个部门,工具依赖(Dependency)人类才能使用,人之间的朋友关系就是关联(Association)。
示例说明
-
继承示例:
2.1 单继承
-
组合示例:
class Engine:def __init__(self, horsepower):self.horsepower = horsepowerdef start(self):print("引擎启动。。。")def stop(self):print("引擎停止。。。")class Car:def __init__(self, make, model, engine):self.make = makeself.model = modelself.engine = enginedef start(self):print(f"{self.make} {self.model} 汽车启动。。。")self.engine.start()def stop(self):print(f"{self.make} {self.model} 汽车停止。。。")self.engine.stop()# 创建一个Engine对象 engine = Engine(200)# 创建一个Car对象,将Engine对象作为参数传入 car = Car("丰田", "凯美瑞", engine)# 启动汽车 car.start()# 停止汽车 car.stop()
-
聚合示例:
当创建两个
Employee
对象时,它们共享同一个Department
对象。两个员工对象独立存在,但它们都包含对同一个部门对象的引用。这就是聚合关系的特点。聚合关系常用于描述整体与部分之间的关系,但两者之间可以独立存在,且部分对象可以被多个整体对象共享。
# 部门类 class Department:def __init__(self, name):self.name = name# 员工类,多个员工聚合成一个部门 class Employee:def __init__(self, name, department):self.name = nameself.department = department# 创建一个Department对象 engineering_department = Department("技术开发部")# 创建两个Employee对象,共享同一个Department对象 employee1 = Employee("张三", engineering_department) employee2 = Employee("李四", engineering_department)# 输出每个员工所在的部门名称 print(f"{employee1.name} 在 {employee1.department.name} 工作。") print(f"{employee2.name} 在 {employee2.department.name} 工作。")
-
关联示例:
描述了两个类之间的对象相互连接。关联可以是双向的,也可以是单向的。在这种关系中,一个类的对象知道另一个类的对象,并通过这个知识进行交互。但是,彼此之间不强制性地拥有或控制对方。
简单例子:每位作家(Writer)可能会写多本书(Book),而每本书都只能有一位作者。这里就形成了Writer和Book之间的关联关系。
class Writer:def __init__(self, name):self.name = nameself.books = [] # Writer knows about Bookdef write_book(self, book_title):book = Book(book_title, self)self.books.append(book)return bookdef get_books(self):return [book.title for book in self.books]class Book:def __init__(self, title, author):self.title = titleself.author = author # Book knows about Writer# 使用示例 author1 = Writer('George Orwell') book1 = author1.write_book('1984') book2 = author1.write_book('Animal Farm')print(f"{author1.name} has written: {author1.get_books()}")
-
依赖示例:
依赖关系(Dependency)是面向对象编程中的一种使用关系,其中一个类的实现依赖于另一个类的定义。这种关系通常表现为局部变量、方法参数或对静态方法的调用。依赖关系意味着如果一个类改变了,它可能会影响到依赖它的其他类。
# 报告的格式化类 class ReportFormatter:def format(self, report_data):# 假设这里有复杂的格式化逻辑return f"格式化报告: {report_data}"# 报告的生成类 class ReportGenerator:def generate(self, data):formatter = ReportFormatter() # ReportGenerator 依赖于 ReportFormatterformatted_data = formatter.format(data)print(formatted_data)# 使用示例 generator = ReportGenerator() generator.generate("周报")
由于
ReportGenerator
直接创建并使用了ReportFormatter
, 如果后者发生改变(例如构造函数参数发生变化),那么前者也可能需要相应地进行修改以适应这些改动。因此可以说,ReportGenerator
对于ReportFormatter
存在着依赖关系。 -
实现示例:
- 抽象(Abstraction)