面向对象编程

Posted 轻风乘舟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象编程相关的知识,希望对你有一定的参考价值。

面向对象编程

两种编程方式

指令式编程 —> 面向过程(函数)编程 —> 程序比较简单的时候没有任何毛病
编程范式(程序设计的方法论):面向对象编程/函数式编程

类和对象的定义

  • 对象:对象是可以接收消息的实体,面向对象编程就是通过给对象发消息达到解决问题的目标。

    对象 = 数据 + 函数(方法) —> 对象将数据和操作数据的函数从逻辑上变成了一个整体。
    一切皆为对象
    对象都有属性和行为
    每个对象都是独一无二的
    对象一定属于某个类
  • 类(类型): 将一大类对象共同的特征(静态特征和动态特征)抽取出来之后得到的一个抽象概念。

    简单的说,类是对象的蓝图(模板),有了类才能够创建出这种类型的对象。

面向对象编程三步骤

面向对象编程

  • 1.定义类 —>类的命名使用驼峰命名法(每个单词首字母大写)
    数据抽象:找到和对象相关的静态特征(属性)—> 找名词
    行为抽象:找到和对象相关的动态特征(方法)—> 找动词
  • 2.造对象 具体
  • 3.发消息

一、定义类

适应性代码 在实际开发中不会加 print 不然会高耦合

在Python中,可以使用class关键字加上类名来定义类(类名的命名使用驼峰命名法,即每个单词首字母大写),通过缩进我们可以确定类的代码块,就如同定义函数那样。

self 是代表接收消息的对象

'''
example01
'''

class Student:
    """学生"""

    # 数据抽象 (属性)
    def __init__(self, name, age): #self 学生对象   后面的是参数
        self.name = name          #给对象绑定name 数据
        self.age = age

    # 行为抽象 (方法)
    def eat(self):   #self 代表接收消息的学生对象
        print(f'{self.name}正在吃饭')

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

    def play(self,game_name):
        print(f'{self.name}正在玩{game_name}')

    def watch_av(self):
        if self.age < 18:
            print(f'{self.name}未满18岁,只能看《天线宝宝》')
        else:
            print(f'{self.name}正在观看岛国片')

二、造对象

在前面学的函数与模块知识

此时我们在相同路径下创建新的文件example02

导入example01

'''
example02
'''


from example01 import Student

#第二步 创建对象 ---> 构造器语法 ---> 类名(...,...)
stu1 = Student('王大锤',15)
stu2 = Student('吴某凡',25)

三、发消息

还是在example02中

#第三步:给对象发消息(调用对象的方法)
#Student.study(stu1,'python程序设计')
stu1.study('python程序设计')
stu1.eat()
stu1.watch_av()
stu1.play('斗地主')

stu2.play('选妃')
stu2.watch_av()

案例

import time


# 定义数字时钟类
class Clock(object):
    """数字时钟"""


    def __init__(self, hour=0, minute=0, second=0):
        """初始化方法
        :param hour: 时
        :param minute: 分
        :param second: 秒
        """
        self.hour = hour
        self.min = minute
        self.sec = second

    def run(self):
        """走字"""
        self.sec += 1
        if self.sec == 60:
            self.sec = 0
            self.min += 1
            if self.min == 60:
                self.min = 0
                self.hour += 1
                if self.hour == 24:
                    self.hour = 0

    def show(self):
        """显示时间"""
        return f'{self.hour:0>2d}:{self.min:0>2d}:{self.sec:0>2d}'
    
# 创建时钟对象
clock = Clock(23, 59, 58)
while True:
    # 给时钟对象发消息读取时间
    print(clock.show())
    # 休眠1秒钟
    time.sleep(1)
    # 给时钟对象发消息使其走字
    clock.run()

静态方法、类方法

我们在类里面写的函数,通常称之为方法,它们基本上都是发给对象的消息
但是有的时候,我们的消息并不想发给对象,而是希望发给这个类(类本身也是一个对象)

在创建对象前给类发消息让它决定是否执行,能执行就创建对象。

  • 静态方法 - 发给类的消息 —> @staticmethod —> 装饰器
  • 类方法 - 发给类的消息 —> @classmethod —> 装饰器 —> 第一个参数(cls)是接收消息的类
"""
example05 - 定义三角形的类,提供计算周长和面积的方法

Author: 龚凡
Date: 2021/8/5 0005
"""
import math


# 直接在初始化方法下面写判断条件是可以的,但会造成代码冗余执行效率不加
# 如果条件不满足会导致报错难以继续执行,此时我们就需要使用静态方法让类先判断后才决定是否执行
# class Triangle:
#     def __init__(self, a, b, c):
#         if a + b > c and b + c > a and c + a > b:
#             self.a = a
#             self.b = b
#             self.c = c
#         else:
#             raise ValueError('无效的边长')  # 引发异常
#
#     def perimeter(self):
#         return self.a + self.b + self.c
#
#     def area(self):
#         half = self.perimeter() / 2
#         return math.sqrt(half * (half - self.a) * (half - self.b) * (half - self.c))
#


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 b + c > a and a + c > b

    @staticmethod
    def is_valid(a, b, c):
        return a + b > c and b + c > a and a + c > b

    def perimeter(self):
        return self.a + self.b + self.c

    def area(self):
        half = self.perimeter() / 2
        return (half * (half - self.a) * (half - self.b) * (half - self.c)) ** 0.5


if __name__ == '__main__':
    # 调用静态方法,判断三条边能否构成三角形
    if Triangle.is_valid(3, 4, 5):
        t = Triangle(3, 4, 5)   #创建对象
        print(t.perimeter())    #发消息
        print(t.area())
    else:
        print('无效的边长,无法构造三角形对象')

面向对象解决实际问题

魔术方法

魔术方法(魔法方法)—> 有特殊用途和意义的方法

魔术方法(魔法方法)—> 有特殊用途和意义的方法
init —> 初始化方法,在调用构造器语法创建对象的时候会被自动调用
str —> 获得对象的字符串表示,在调用print函数输出对象时会被自动调用
repr —> 获得对象的字符串表示,把对象放到容器中调用print输出时会自动调用
—> representation
lt —> 在使用 < 运算符比较两个对象大小时会自动调用

如果要限制一个类的对象只能拥有某些属性,可以在类中使用__slots__魔法属性

里面的属性是不可修改的。

class Student:
    __slots__ = ('name', 'gender')
"""
example06 - 扑克游戏,四个玩家参与,先洗牌,再把牌发到四个玩家的手上。

~ 牌(Card)
    - 属性:花色(suite)、点数(face)
    - 行为:显示
~ 扑克(Poker)
    - 属性:保存牌的列表
    - 行为:洗牌(shuffle)、发牌(deal)
~ 玩家
    - 属性:名字(昵称)、保存玩家手牌的列表
    - 行为:摸牌(get)、整理(arrange)

魔术方法(魔法方法)---> 有特殊用途和意义的方法
    ~ __init__ ---> 初始化方法,在调用构造器语法创建对象的时候会被自动调用
    ~ __str__ ---> 获得对象的字符串表示,在调用print函数输出对象时会被自动调用
    ~ __repr__ ---> 获得对象的字符串表示,把对象放到容器中调用print输出时会自动调用
        ---> representation
    ~ __lt__ ---> 在使用 < 运算符比较两个对象大小时会自动调用

如果一个变量的取值只有有限个选项,可以考虑使用枚举类型。
Python中没有定义枚举类型的语法,但是可以通过继承Enum类来实现枚举类型。
结论1:枚举类型是定义符号常量的最佳选择!!!
结论2:符号常量(有意义的名字)总是优于字面常量!!!

Author:
Date: 2021/8/5
"""

from enum import Enum


# 枚举类型
class Suite(Enum):
    SPADE, HEART, CLUB, DIAMOND = range(4)


class Card:
    """牌"""

    def __init__(self, suite, face):
        self.suite = suite
        self.face = face

    def __str__(self):
        return self.show()

    def __repr__(self):
        return self.show()

    def __lt__(self, other):   #两个牌做比较 利于下面整理牌
        if self.suite == other.suite:
            return self.face < other.face
        return self.suite.value < other.suite.value

    def show(self):
        """显示"""
        suites = ['♠️', '❤️', '♣️', '♦️']
        faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
        return f'{suites[self.suite.value]}{faces[self.face]}'


def main():
    """程序入口"""
    card1 = Card(Suite.HEART, 1)
    card2 = Card(Suite.SPADE, 13)
    print(card1, card2)
    print(card1 is card2)
    card3 = Card(Suite.DIAMOND, 9)
    card4 = Card(Suite.CLUB, 11)
    print(card3.show(), card4.show())
    card1 = card2
    print(card1, card2, card3)
    # 身份运算符
    print(card1 is card2)
    print(card1 is card3)
    cards = [card1, card2, card3, card4]
    print(cards)


if __name__ == '__main__':
    main()
"""
example07 - 扑克

Author: 
Date: 2021/8/6
"""
import random

from example06 import Card
from example06 import Suite


class Poker:
    """扑克"""

    def __init__(self):
        self.cards = [Card(suite, face)
                      for suite in Suite
                      for face in range(1, 14)]
        self.counter = 0

    def shuffle(self):
        """洗牌"""
        self.counter = 0
        random.shuffle(self.cards)

    def deal(self) -> Card:
        """发牌"""
        card = self.cards[self.counter]
        self.counter += 1
        return card

    def has_more(self) -> bool:
        """是否还有牌"""
        return self.counter < len(self.cards)


def main():
    poker = Poker()
    poker.shuffle()
    while poker.has_more():
        print(poker.deal(), end=' ')


if __name__ == '__main__':
    main()
from example07 import Poker


class Player:
    """玩家"""

    def __init__(self, nickname):
        self.nickname = nickname
        self.cards = []

    def get_one_card(self, card):
        """摸一张牌"""
        self.cards.append(card)

    def arrange(self):
        """整理手上的牌"""
        self.cards.sort()

    def show(self):
        """显示玩家手上的牌"""
        print(self.nickname, end=': ')
        for card in self.cards:
            print(card, end=' ')
        print()


def main():
    nicknames = ('东邪', '西毒', '南帝', '北丐')
    players = [Player(nickname) for nickname in nicknames]
    poker = Poker()
    poker.shuffle()
    # 将牌发到四个玩家的手上
    for _ in range(13):
        for player in players:
            card = poker.deal()
            player.get_one_card(card)
    # 显示四个玩家手上的牌
    for player in players:
        player.arrange()
        player.show()


if __name__ == '__main__':
    main()

面向对象四大支柱

面向对象编程的四大支柱:

  • ~ 抽象(abstraction):提取共性(定义类就是一个抽象过程,需要做数据抽象和行为抽象)。

  • ~ 封装(encapsulation):把数据和操作数据的函数从逻辑上组装成一个整体(对象)。
    —> 隐藏实现细节,暴露简单的调用接口。

  • ~ 继承(inheritance):扩展已有的类创建新类,实现对已有类的代码复用。

  • ~ 多态(polymorphism):给不同的对象发出同样的消息,不同的对象执行了不同的行为。
    —> 方法重写:子类对父类已有的方法,重新给出自己的实现版本

抽象和封装在前面我们已经学习了,现在来学习继承和多态。

继承

继承:对已有的类进行扩展创建出新的类,这个过程就叫继承。
提供继承信息的类叫做父类(超类、基类),得到继承信息的类称为子类(派生类)。

注意 !!

继承是实现代码复用的一种手段,但是千万不要滥用继承。

子类直接从父类继承公共的属性和行为,再添加自己特有的属性和行为,
所以子类一定是比父类更强大的,任何时候都可以用子类对象去替代父类对象。

Python中的继承允许多重继承,一个类可以有一个或多个父类。
如果不是必须使用多重继承的场景下,请尽量使用单一继承。

在下列例子中 我们定义Person为父类类,子类为三个,分别是Student、Teacher、Programmer、它们都有三个共同的行为是 eat 、play、introduce。所以创建父类来装这些行为,这样有利于减少代码。


class Person:
    """人"""

    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def eat(self):
        """吃饭"""
        print(f'{self.name}正在吃饭.')

    def play(self, game_name):
        """玩"""
        print(f'{self.name}正在玩{game_name}.')

    def introduce(self):
        """自我介绍"""
        sex = "男" if self.gender else "女"
        print(f'我叫{self.name}, 是一个{sex}人.')


class Student(Person):
    """学生"""

    def __init__(self, name, gender, grade):
        super().__init__(name, gender)
        self.grade = grade

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


class Teacher(Person):
    """老师"""

    def __init__(self, name, gender, title):
        super().__init__(name, gender)
        self.title = title

    def teach(self, course_name):
        """教课"""
        print(f'{self.name}{self.title}正在讲授{course_name}.')


class Programmer(Person):
    """程序员"""

    def write_code(self, programming_language):
        """写代码"""
        print(f'{self.name}正在用{programming_language}写代码.')


stu = Student('王大锤', True, '五年级')
stu.study('语文')
stu.play('王者荣耀')
stu.eat()
stu.introduce()

teacher = Teacher('王大锤', True, '叫兽')
teacher.eat()
teacher.play('斗地主')
teacher.teach('Python程序设计')
teacher.introduce()

programmer = Programmer('白元芳', True)
programmer.eat()
programmer.play('吃鸡')
programmer.write_code('Python')

多态

~ 多态(polymorphism):给不同的对象发出同样的消息,不同的对象执行了不同的行为。
—> 方法重写:子类对父类已有的方法,重新给出自己的实现版本

在父类Employee中拥有get_salary行为,且三个子类中也有相同的行为但实现过程不同,运行下代码会看出。

子类对父类已有的方法,重新给出自己的实现版本,这个过程叫做方法重写(override)。
在重写方法的过程中,不同的子类可以对父类的同一个方法给出不同的实现版本,那么该方法在运行时就会表现出多态行为。

'''
三类员工:
~ 部门经理:固定月薪,15000元
~ 程序员:计时结算月薪,每小时200元
~ 销售员:底薪+提成,底薪1800元,销售额5%提成

录入员工信息,自动结算月薪
'''

from abc import abstractmethod


class Employee:

    def __init__(self, no, name):
        self.no = no
        self.name = name

    @abstractmethod
    def get_salary

以上是关于面向对象编程的主要内容,如果未能解决你的问题,请参考以下文章

面向面试编程代码片段之GC

Scala的面向对象与函数编程

面向对象编程

面向过程编程与面向对象编程

Java面向对象

面向对象编程(OOP)