Pyhton基础学习系列15——面向对象编程

发布时间:2024年01月11日

一、面向对象编程

  • 面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它使用对象作为基本的编程单元,将数据和操作数据的方法封装在一起。在面向对象编程中,程序被组织为对象的集合,这些对象可以相互交互,通过定义类和创建对象来实现。
  • 我们现在生活中有两种主流的编程范式,一种是面向过程,一种是面向对象,面向过程:遇到问题自己想办法解决问题;面向对象:遇到问题马上想有没有一个已经存在的对象拥有解决这个问题的能力。如果有,直接拿过来用,没有再自己解决。
  • 简单来说就是在我们面对问题时,如果要解决一个洗衣服的问题,面对对象编程的思想就是先定义一个洗衣机类,创建洗衣机和人这两个对象,针对洗衣机这个对象提供一系列属性和方法:加水、清洗、甩干、烘干等;针对人这个对象提供一系列属性和方法:性别、年龄、名字、经验、加脏衣服、加洗衣液等方法。然后每次需要洗衣服时就调用这个类给这两个对象发消息就可以解决。

二、如何定义类和创建对象

1.类

  • 自定义的类基本都使用大驼峰命名法来对类命名,也就是首字母大写。
  • 使用class关键字 + 类名来定义类
  • 类 (Class):类是一种抽象数据类型,用于描述具有相同属性和行为的对象集合。它定义了对象的属性(称为成员变量或属性)和行为(称为方法或函数)。类可以看作是创建对象的蓝图或模板,它定义了对象应该具有的特征和行为。
  • 一个类可以有多个对象
  • 简单来说类就是可以理解为一种具有相似的内部状态和运动规律抽象的集合,就比如把汽车当做一个类,它可能有属性(成员变量)如车型、颜色、速度等,也可能有方法(行为)如启动、加速、刹车等。当创建一个实际的汽车对象时,该对象就是类的一个实例,拥有特定的车型、颜色和可以执行的操作。
# 定义一个学生类
class Student:
    """学生类"""
    pass

2.对象

  • 对象 (Object):对象是类的实例化,是类的具体实体。当类被实例化时,就创建了一个对象,该对象拥有该类定义的所有属性和方法。对象是类的具体表现,每个对象都有自己的状态(属性值)和行为(可以调用的方法)。
  • 调用对象的方法,即给对象发消息:方式一:对象.方法方式二:类.方法(参数1,参数2)
class Student:
    """学生类"""

    def study(self, course_name):
        print(f'学生正在学习{course_name}')

    def sleep(self):
        print(f'学生正在睡觉')
# 创建实例对象
s1 = Student()

# 方式一:对象.方法
s1.sleep()
# 学生正在睡觉
s1.study('Python')
# 学生正在学习Python程序设计

# 方式二:类.方法(参数1,参数2)
# 参数1:接收消息的对象,参数2:你要发的消息。
Student.study(s1, 'Python')
# 学生正在学习Python程序设计

3.初始化方法

  • 命名为__init__
  • 它是一个特殊的方法,用于在创建类的实例(对象)时进行初始化操作。这个方法会在对象创建后立即被调用,执行一些必要的设置或赋初值的操作。
  • 初始化方法的第一个参数是self,它表示对象本身。随后的参数可以用来接收创建对象时传递的参数。
class Student:
    """学生类"""
    
    # 初始化方法
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def study(self, course_name):
        print(f'{self.name}正在学习{course_name}')


    def sleep(self):
        print(f'{self.name}正在睡觉')


# 创建实例对象
s1 = Student('是靖不是静', 22)

s1.study('Python')
# 是靖不是静正在学习Python
s1.sleep()
# 是靖不是静正在睡觉

三、面向对象编程中三种不同类型的方法

1.对象方法(实例方法)

  • 类中定义的方法都是对象方法,对象方法可以访问对象属性,只能通过实例化的对象来调用。
  • 对象方法通过对象实例调用,自动传递实例对象作为第一个参数。(对象名.对象方法)
  • 应用场景: 当方法需要访问和操作特定对象的实例变量时,通常使用对象方法。

2.类方法

  • 可以访问类属性,使用 @classmethod 装饰器进行标识,第一个参数通常是 cls,表示类本身。
  • 类方法可以通过对象名或类名调用,自动传递类本身作为第一个参数。(类名.类方法 或者 对象名.类方法)
  • 应用场景:当方法需要访问或修改类级别的变量时,通常使用类方法。类方法对所有实例共享,并可以在不创建实例的情况下调用。

3.静态方法

  • 既不能访问类属性也不能访问对象属性,使用 @staticmethod 装饰器进行标识,它不需要表示实例或类的特殊参数。
  • 静态方法可以通过对象名或类名调用,与普通函数类似,不传递特殊的实例或类参数。(类名.静态方法 或者 对象名.静态方法)
  • 应用场景:当方法既不需要访问实例变量,也不需要访问类变量时,通常使用静态方法。静态方法与类或实例无关。

4.三种方法的用法

# 案例:传入三条边,判断能否构成三角形,并且计算出三角形的面积和周长
class Triangle:
    """三角形类"""

    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    # 要在实例化之前先判断三条边能否组成三角形

    # 定义类方法(装饰器)
    # 语法糖@
    # @classmethod
    # def is_valid(cls, a, b, c):
    #     return a + b > c and a + c > b and b + c > a
    # 这里静态方法和类方法实现的效果是一样的

    # 定义静态方法
    @staticmethod
    def is_valid(a, b, c):
        return a + b > c and a + c > b and b + c > a

    # 对象方法
    def perimter(self):
        """计算三角形周长"""
        return f'周长:{self.a + self.b + self.c}'

    # 对象方法
    def area(self):
        """计算三角形面积"""
        # 海伦公式(秦九韶公式):根据三条边计算面积
        p = (self.a + self.b + self.c) / 2
        return f'面积:{(p * (p - self.a) * (p - self.b) * (p - self.c)) ** 0.5}'


a, b, c = 1, 1, 5

# 这里先用类方法或者静态方法去判断三条边是否能组成三角形,如果可以再创建实例对象

if Triangle.is_valid(a, b, c) == True:
    t1 = Triangle(a, b, c)
    print(t1.perimter())
    print(t1.area())
else:
    print('三条边不能组成三角形')
# 三条边不能组成三角形

# 如果是a, b, c = 3, 4, 5则输出
# 周长:12
# 面积:6.0
  • 总的来说,对象方法主要操作实例变量,类方法主要操作类变量,静态方法则不依赖于实例或类的特殊参数。选择使用哪种类型的方法取决于具体的需求和设计。

四、面向对象的三大支柱

1.封装(Encapsulation)

  • 封装(Encapsulation):定义类就是在封装,把数据和操作数据的函数封装成一个整体,这个整体叫做对象。
  • 隐藏实现细节,通过对象的公共接口(方法)来访问和修改数据。(不需要知道类的实现细节,只需要创建对象发出消息就可以解决问题)。

2.继承(Inheritance)和重写(override)

  • 继承(inheritance):从已有的类创建新类的过程,在继承的过程中子类可以添加、修改或扩展它们。
  • 重写(Override):是指在子类中重新定义(覆盖)父类中已经存在的方法。简而言之,当子类定义了一个与父类同名的方法时,子类的方法会覆盖父类的方法,从而改变了该方法的实现。

3.多态(Polymorphism)

  • 多态(Polymorphism)允许对象在运行时表现出多种形态,简单来说,不同的对象在接收到相同的消息时,会表现出不同的行为

4.例子

  • 封装:这段代码中,每个员工类型(Manager、Programmer、Salesman)都封装了自己的具体实现和数据成员。例如,Manager类封装了固定的工资,Programmer类封装了工作小时数,而Salesman类封装了销售额。
  • 继承:Manager、Programmer、Salesman类都继承自Employee类,这意味着它们都继承了Employee类的属性和方法,通过继承,这些类可以重用共同的代码,同时可以在子类中添加特定于自己的属性和方法。
  • 多态:通过使用抽象类Employee和多态的概念,所有不同类型的员工实例都可以通过统一的接口get_salary被调用,而具体的实现取决于实际的对象类型,这使得在main函数中循环遍历员工列表时,直接调用get_salary方法即可。
  • 重写:Manager类重写了Employee类中的get_salary方法,并提供了自己的实现,即返回固定的工资值15000。同样,Programmer和Salesman类也分别重写了get_salary方法,提供了根据工作小时数和销售额计算工资的实现。
from abc import abstractmethod, ABC

# Employee 类是一个抽象类,其中定义了一个抽象方法 get_salary,表示获取员工工资的接口。
# 抽象类不能被实例化,而是用于定义其他类的共同接口。
class Employee(ABC):
    """员工(抽象类)"""
    # 初始化方法
    def __init__(self, name):
        self.name = name

    # @abstractmethod被用于标记Employee类中的get_salary方法(Python中用于定义抽象方法的装饰器)
    # 这表示Employee类是一个抽象类,并且所有继承自Employee的子类都必须实现这个get_salary方法
    # 否则会导致子类无法实例化。
    @abstractmethod
    def get_salary(self):
        pass


# Manager类继承自Employee类,实现了get_salary方法。
# 部门经理的工资是固定的,为15000元。
class Manager(Employee):
    """部门经理"""

    def get_salary(self):
        return 15000

# Programmer类也继承自Employee类,但它在构造函数中添加了一个属性working_hour表示工作小时数。
class Programmer(Employee):
    """程序员"""

    def __init__(self, name):
        # 这里的super().__init__(name)表示调用父类Employee的构造函数,并传递name参数进行初始化。
        # 这样做的好处是,子类可以在自己的构造函数中执行一些特定于子类的初始化操作,而同时确保父类的初始化也得到了执行。
        super().__init__(name)
        self.working_hour = 0

    # get_salary方法计算工资根据工作小时数。
    def get_salary(self):
        return 200 * self.working_hour

# Salesman类类似,但它有一个属性sales表示销售额,工资计算中包括固定部分和销售提成
class Salesman(Employee):
    """销售员"""

    def __init__(self, name):
        super().__init__(name)
        self.sales = 0

    def get_salary(self):
        return 2000 + self.sales * 0.05


def main():
    # 员工数据
    emps = [Manager('曹操'), Manager('刘备'), Programmer('荀彧'),
            Salesman('典韦'), Salesman('张飞'), Programmer('庞统'),
            Salesman('关羽')]
    # 遍历整个数据列表拿到每个员工
    for emp in emps:
        # 判断员工的类型,如果是程序员则要输入一个工作时间
        if isinstance(emp, Programmer):
            emp.working_hour = int(input(f'输入{emp.name}本月工作时间: '))
        # 判断是否为销售员,如果是则输入销售额
        elif isinstance(emp, Salesman):
            emp.sales = int(input(f'输入{emp.name}本月销售额: '))
        # 其他的都是部门经理直接输出
        print(f'{emp.name}: {emp.get_salary():.2f}元')


if __name__ == '__main__':
    main()
    
# 曹操: 15000.00元
# 刘备: 15000.00元
# 输入荀彧本月工作时间: 180
# 荀彧: 36000.00元
# 输入典韦本月销售额: 50000
# 典韦: 4500.00元
# 输入张飞本月销售额: 60000
# 张飞: 5000.00元
# 输入庞统本月工作时间: 200
# 庞统: 40000.00元
# 输入关羽本月销售额: 3000
# 关羽: 2150.00元

总结

面向对象编程的优点包括代码的重用性、可维护性、灵活性和抽象能力。通过使用类和对象,程序可以更自然地模拟真实世界中的问题,并更容易理解和扩展,在解决一些大型项目或者比较困难的问题时使用,具体问题具体分析,简单的问题是不推荐使用面向对象编程进行解决的。

文章来源:https://blog.csdn.net/qq_49771146/article/details/135440303
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。