进阶7:面向对象思想

面向对象简介

面向对象的思想(Object-Oriented Thinking,简称OOT)是一种将现实世界中的事物、概念或问题抽象为对象的思维方式。通过对象及其交互来组织程序,从而更好地模拟和解决实际问题。面向对象的思想不仅是编程语言的特性,也是设计和解决问题的哲学,它让我们能够以更加自然和直观的方式处理复杂的问题。

面向对象的核心思想

抽象(Abstraction) 抽象是指提取事物的共性,将复杂的现实问题简化为模型。在面向对象中,抽象通过定义类来实现,将类看作现实世界中的某一类事物,并用类的属性和方法来描述事物的特征和行为。通过抽象,我们忽略掉不必要的细节,专注于问题的核心。

  • 例子:例如“汽车”这个概念,我们不需要每次都讨论“汽车”的每一个细节,而是将“汽车”抽象为一个类,包含如“品牌”,“颜色”,“速度”等属性,以及“启动”,“加速”,“刹车”等方法。

封装(Encapsulation) 封装是将数据和操作这些数据的代码封装在一个对象中,对外提供接口而隐藏实现细节。这不仅提高了代码的可维护性,还能保证对象状态的有效性和一致性。

  • 例子:如果我们有一个“银行账户”的对象,银行账户内部可能有余额、账户号等属性,我们通常不允许外部直接修改余额,而是通过“存款”和“取款”方法来进行操作。这样封装了对账户的操作逻辑,防止了直接更改数据的错误。

继承(Inheritance) 继承是面向对象思想中的一个重要机制,允许我们创建新类时复用现有类的功能。通过继承,子类可以继承父类的属性和方法,并可以在此基础上进行扩展或修改。继承体现了“父类-子类”的关系。

  • 例子:假设我们有一个“动物”类,其中有“吃饭”和“睡觉”等方法,然后我们可以派生出“狗类”和“猫类”,这两个类继承“动物类”的特性,但也可以根据自己的需要添加特有的行为(比如“汪汪叫”或“喵喵叫”)。

多态(Polymorphism) 多态是指同一个方法调用可以表现出不同的行为,具体的行为取决于调用时的对象类型。多态使得代码更加灵活,能够处理不同类型的对象,而不需要在代码中明确指定每个对象的类型。

面向对象的思想的好处

  1. 提高程序的可维护性和可扩展性
    由于对象通过封装隐藏了复杂的实现,外部代码只关心对象的接口(方法),而不需要关心内部实现。当需要修改对象的实现时,只需要改变类内部代码,外部代码不受影响。
  2. 增强代码复用性
    通过继承机制,可以让子类复用父类的代码,并且可以在子类中扩展或修改父类的功能。这使得代码更加简洁,避免重复编写相同的代码。
  3. 更加符合现实世界的建模方式
    面向对象的思维方式非常符合人们对世界的理解和思考方式,因为它将实际的物理或抽象事物抽象成对象,这让程序设计更加直观和易于理解。
  4. 提高代码的灵活性
    通过多态,不同类型的对象可以共享相同的接口,这使得系统能够灵活处理不同类型的对象,提高了代码的灵活性和可扩展性。

详细讲述下封装、继承、多态三个概念

封装(Encapsulation)

封装是指将对象的状态(属性)和行为(方法)组合在一起,并控制它们的访问。通常,类的内部数据是私有的,外部只能通过公开的接口(如方法)来访问和修改数据。

例如,在Dog类中,我们通过方法bark()来控制狗对象的行为,而不能直接通过外部访问它的属性。可以通过设置访问控制来实现封装。

继承(Inheritance)

继承是面向对象编程的一个重要特性,它允许我们通过现有的类创建新的类。新类可以继承现有类的属性和方法,也可以新增或重写一些功能。

例如,假设我们想创建一个Cat类,它也有bark()方法,但我们希望它的行为与Dog不同。我们可以通过继承来实现:

class Cat(Dog):
    def bark(self):
        print(f"{self.name} says meow!")

这样,Cat类继承了Dog类的属性和方法,但我们修改了bark()方法以适应猫的特点。

多态(Polymorphism)

多态指的是相同的接口或方法可以根据不同的对象类型表现出不同的行为。比如,DogCat对象都可以调用bark()方法,但它们表现出的行为不同。

class Dog:
    def bark(self):
        print("Woof")

class Cat:
    def bark(self):
        print("Meow")

def make_sound(animal):
    animal.bark()

make_sound(Dog())  # 输出 Woof
make_sound(Cat())  # 输出 Meow

类与对象

1. 类(Class)

类是对象的蓝图或模板,它定义了对象的属性和方法。类本质上是对一组相似对象的抽象,它包含两个部分:

  • 属性(Attributes):对象的特征或状态,通常表现为变量。
  • 方法(Methods):对象的行为或功能,通常表现为函数。

类的设计

进阶7:面向对象思想

下图中的类:

进阶7:面向对象思想
植物类
	射手类
		双发豌豆射手
		寒冰射手
			血量:5
			(发射炮弹)
			(减速)
	坚果类
		圆坚果
		高坚果
	近战类
		大嘴花
	生产类
		向日葵
	消耗品类
		土豆雷
		樱桃炸弹
		毁灭炸弹
僵尸类
	近战僵尸
		铁桶僵尸
	远程僵尸
		投石车僵尸
	。。。

地图类
	房顶
	夜晚
	后院

定义类

在 Python 中,类是通过 class 关键字来定义的:

# 经典类(旧式类)定义形式
class Person:
class Person():

# 新式定义
class Person(object):

设计类

class 类名:
    # 方法列表
    # 类的方法,类的方法和函数的区别之一就在于有默认有一个参数self,可以这么理解:出现在类外的叫做函数,出现在类里面的都叫做方法
    def 方法1(self):
        print("我是方法一")
    
    def 方法 2(self):
        print("我是方法二")

例如:

# 抽象一个人类
class Person:
    # 第一个方法
    def eat(self, food):
        print('一个人在吃',food)
    # 第二个方法
    def sleep(self,t):
        print('一个人睡了', t, '个小时')

2. 对象(Object)

对象是现实世界事物的抽象,它通常包括:

  • 属性(Attributes):对象的特征,通常表现为数据或状态。
  • 方法(Methods):对象可以执行的操作或行为。

创建对象

通过类来创建对象时,我们通常会调用类名并传入所需的参数:

格式:

# 创建对象格式:
对象名 =  类名(参数列表...)
# 对象调用方法格式:
对象名.方法名(参数列表)

通过类,我们可以实例一个对象,实例对象时,会在内存中分配一块内存空间,这个空间就是这个对象的位置,然后将引用这个地址返回给对象名:

实例对象:

Tom = Person()

验证对象名是否返回值为地址:

进阶7:面向对象思想

在当前模块下 的Person 的类创建了一个 对象(Tom)object,开辟的新地址为0x10b6d4190

对象执行类中的方法

进阶7:面向对象思想

实例化多个对象

首先我们需要知道的是,每实例一个对象,都会开辟一个新的地址

进阶7:面向对象思想

每个对象调用类中的方法:

进阶7:面向对象思想

这样就可以实现多个对象使用方法了,但是问题来了,输出的内容中怎么区分 Tom 和 Jack 呢?

动态绑定多个属性

刚才我们已经可以确认,Tom 和 Jack 并不是同一个对象(因为指向了不通的地址),创建的是多个类型相同,但是地址不同的多个对象

我们尝试修改下代码,为对象动态添加一个属性 name:

动态为对象绑定属性时,给哪个对象绑定了属性,哪个对象才会有该属性,其他对象没有此属性。如果再方法中引用了该属性,没有该属性的对象调用这个方法就会报错

进阶7:面向对象思想

上述原理图:

进阶7:面向对象思想

这种属性绑定方式其实是创建完对象才进行绑定,那么上面这个多次绑定对象属性的过程太麻烦了,能不能在创建对象的时候就绑定这些属性呢?

初始化方法中绑定属性(__init__方法)

在Python中,__init__方法是一个特殊的成员方法(魔法方法),通常用于初始化对象。它是类的构造方法,当类的实例被创建时不需要手动调用,而是会自动调用。__init__方法允许你在创建对象时自动调用进行一些基础的设置,如初始化属性。

下面这段代码来证明__init__方法在对象创建时会被自动调用:

进阶7:面向对象思想

利用__init__的特征,可以在实例对象时就绑定一些属性,在创建对象时,括号内添加初始化对象的参数列表,实参通过__init__方法的形参传递变量

class Person:
    # 类的构造函数,用于初始化对象
    def __init__(self, name, age):
        self.name = name  # 实例变量,保存名字
        self.age = age  # 实例变量,保存名字

    # 第一个方法
    def eat(self, food):
        print(self.name, '在吃', food)

    # 第二个方法
    def sleep(self, t):
        print(self.name, '睡了', t, '个小时')

# 创建一个 Tom 实例
Tom = Person('Tom','18')
Tom.eat('饭')
Tom.sleep(8)

# 创建一个 Jack 实例
Jack = Person('Jack','22')
Jack.eat('海鲜')
Jack.sleep('10')
进阶7:面向对象思想

我们一直没有强调 self 到底是什么,这里重点说一下

在调用方法时,方法的第一个参数 self 是不用手动传参的,这个参数会由解释器自动调用该方法的对象传递过去,即使self 修改其他变量名称也可以,因为每创建一个对象都可以使用相同类下的属性和方法,self 变量就承担起了传递当前对象地址的重担,当有一个对象创建时,会开辟一个新地址,利用self可以实现地址的传递,从而调用属性和方法,所以可以这么说:self 代表当前对象,谁来实例这个类,self 就是谁

进阶7:面向对象思想

这里相当于 self= Tom

__str__方法格式化对象

在 Python 中,__str__ 方法用于定义一个类实例在str() 函数调用或在使用 print() 函数打印时的字符串表示。这个方法是一个特殊方法(也称为魔法方法或双下划线方法),它提供了一种为对象提供可读性更强的字符串表示的途径。

这里有一个简单的示例,展示如何在一个类中实现 __str__ 方法:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __str__(self):
        return f"Person(name={self.name}, age={self.age})"

# 创建一个 Person 对象
person = Person("Alice", 30)

# 使用 print 输出对象信息
print(person)  # 输出: Person(name=Alice, age=30)

# 使用 str() 函数获取对象的字符串表示
person_str = str(person)
print(person_str)  # 输出: Person(name=Alice, age=30)
进阶7:面向对象思想

因为默认没有实现__str__方法,那么会打印:模块名 类名 object at 0x10ccd0f40

如果想按自己的格式显示,需要在类中实现该方法

  • 该函数必须有一个返回值
  • 并且这个返回值必须是一个字符串

如果需要将对象的信息按照一定格式格式化,可以在这里进行格式修饰,修饰完后,可以将这个格式化字符串返回,让 str()方法在执行时,得到该对象转换的字符

__str__方法作用:

__str__ 方法返回一个格式化的字符串,该字符串包含了对象的一些有用信息。当 print(person) 被调用时,Python 在幕后使用 str(person),这又会调用 person.__str__(),从而得到我们定义的字符串格式。

调用该方法的场景:

  • 通过 print()函数打印时,会自动调用该方法
  • 通过 str()函数对定义的对象进行类型转换时,会调用该方法

__del__方法

在Python中,__del__方法是一个特殊的方法,称为析构方法(destructor)。它在一个对象的生命周期结束时被调用,用于执行一些清理操作,比如关闭文件、断开网络连接或者释放其他外部资源。__del__方法在对象被垃圾收集器回收时触发。然而,由于Python使用自动的垃圾回收机制,开发者通常不需要手动管理内存,也就是说,在大多数情况下不需要定义__del__方法。

  • 该方法是用来在销毁对象时,回收释放资源使用的方法。也是自动调用
  • 当在使用 del 对象时,会调用方 法

示例

class MyClass:
    def __init__(self, resource):
        self.resource = resource
        print(f"{self.resource} has been acquired.")

    def __del__(self):
        print(f"{self.resource} has been released.")

# 使用示例
obj = MyClass("SomeResource")
del obj

在这个示例中,创建一个MyClass实例时会获取某种资源,并在该对象被删除时释放这个资源(提前手动释放)。

进阶7:面向对象思想

不主动释放地址,因为垃圾回收机制,程序运行完毕会回收内存地址

进阶7:面向对象思想

python引用计数器原理以及__del__销毁过程

class Pyobj:
    def __del__(self):
        print("对象被销毁")

print("1")
obj = Pyobj()
obj = 6     # 让变量obj指向其他对象
print("2")

当obj = 6 这行被执行时,__del__方法会被执行,print("对象被销毁") 会先于print("2") 执行。
程序输出结果

1
对象被销毁
2

下图从内存层面上展示了这一过程

进阶7:面向对象思想

面向对象项目案例:

下面是一个关于烤地瓜和烤箱的面向对象的 Python 代码示例。这个示例包含了两个类:SweetPotatoOvenSweetPotato 类表示一个地瓜,具有状态和烹饪时间。Oven 类表示一个烤箱,可以添加地瓜并开始烤制。

案例要求:

1、食物有自己的状态,默认是生的

2、食物有自己烧烤的总时间,由每次烧烤时间累加得出

3、不同食物烧烤时,状态随着烧烤总时间的变化而改变。代码中只体现两类食物
例如:

  • 薯类:[0,4)生的[4,7)熟了[7,+♾️)糊了,
  • 肉类:[0,6)生的[6,10)熟了[10,+♾️)糊了

4、食物可以添加自己想要添加的调料

骤构建类、属性、方法:

# 定义食物类
class FoodItem:
	## 食物属性初始化方法(name、type)
		# 食物名称name
		# 食物类型type(薯类、肉类)
		# 烤制时间累加器cooked_time
		# 状态属性state(default = 生的)
		# 附加调料属性condiment(default = [])

	## 食物方法
	# 判断食物状态(time)
		# 添加烧烤时间,判断状态
	# 添加调料(condiment)
		# 列表添加调料

	## 格式化输出

# 定义烤箱
class Oven:
	## 烤箱属性初始化方法
		# 食物填充状态

	## 烤箱方法
	# 添加食物(food_item引入食物对象)
	# 烘烤食物(time)
class FoodItem:
    def __init__(self, name, food_type):
        # 初始化方法,设置食物的名字和类型
        self.name = name  # 食物名称
        self.food_type = food_type  # 食物类型(如地瓜、肉类)
        self.cooked_time = 0  # 记录烹饪时间的初始值为0
        self.status = "生的"  # 初始状态为"生的"
        self.condiments = []  # 初始化调料列表为空

    def cook(self, time):
        # 根据不同类型的食物判断烹饪后的状态
        self.cooked_time += time  # 增加烹饪时间
        if self.food_type == "薯类":
            if self.cooked_time < 4:
                self.status = "生的"
            elif self.cooked_time < 7:
                self.status = "熟了"
            else:
                self.status = "烤糊了"

        elif self.food_type == "肉类":
            if self.cooked_time < 6:
                self.status = "生的"
            elif self.cooked_time < 10:
                self.status = "熟了"
            else:
                self.status = "烤糊了"

    def add_condiment(self, condiment):
        # 添加调料到食物
        self.condiments.append(condiment)

    def __str__(self):
        # 定义字符串表示,可用于打印输出对象信息
        condiments_str = ', '.join(self.condiments) if self.condiments else '无'
        return f"{self.name} 状态: {self.status}, 调料: {condiments_str}"


class Oven:
    def __init__(self):
        # 初始化,没有食物
        self.food_item = None

    def insert_food(self, food_item):
        # 将食物放入烤箱
        self.food_item = food_item
        print(f"{food_item.name} 已放入烤箱。")

    def bake(self, time):
        # 烘烤指定时间
        if self.food_item:
            print(f"烤箱开始烤制 {self.food_item.name} {time} 分钟...")
            self.food_item.cook(time)  # 调用食物对象的cook方法
            print("烤制完成。")
        else:
            print("烤箱里没有食物!")


# 使用示例
# 创建一个地瓜对象
my_sweet_potato = FoodItem("地瓜", "薯类")
# 创建一个烤箱对象
my_oven = Oven()
my_oven.insert_food(my_sweet_potato)
# 烘烤3分钟
my_oven.bake(3)
# 打印地瓜状态
print(my_sweet_potato)
# 添加蜂蜜调料
my_sweet_potato.add_condiment("蜂蜜")
# 再烘烤3分钟
my_oven.bake(3)
print(my_sweet_potato)
# 添加辣椒粉调料
my_sweet_potato.add_condiment("辣椒粉")
# 再烘烤2分钟
my_oven.bake(2)
print(my_sweet_potato)

# 烤其他食物
# 创建一个鸡肉对象
my_chicken = FoodItem("鸡肉", "肉类")
my_oven.insert_food(my_chicken)
# 烘烤5分钟
my_oven.bake(5)
print(my_chicken)
# 再烘烤5分钟
my_oven.bake(5)
print(my_chicken)

类的复合

类的复合(Composition) 是面向对象编程中的一种设计方法,指的是一个类通过将其他类的对象作为成员变量,来实现复杂功能的一种方式。

与继承不同,复合更关注于有一个(Has-A)关系而非是一个(Is-A)关系。例如,一个 Car 类可以包含一个 Engine 对象作为其成员,表示汽车有一个发动机。

class Engine:
    def start(self):
        return "Engine started."

class Car:
    def __init__(self):
        self.engine = Engine()  # Car has an Engine instance

    def start(self):
        return self.engine.start()

# 使用示例
my_car = Car()
print(my_car.start())  # 输出: Engine started.

在这个例子中,Car类包含了一个Engine类的实例。Car类通过复合关系来使用Engine类的功能,这也使得Car类可以通过其engine属性来访问Engine类的方法。

发布者:LJH,转发请注明出处:https://www.ljh.cool/41863.html

(0)
上一篇 2024年11月12日 上午10:30
下一篇 2022年12月29日 上午2:52

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注