面向对象

浏览 2014

课文

面向对象编程

面向对象编程这个概念,刚接触过编程的同学可能都会听说过。而 python 从设计之初就已经是一门面向对象的语言,也正因为如此我们可以很方便地在 python 中使用面向对象编程的思想。

与面向对象相对的是面向过程编程,传统的 C 语言就是面向过程为主。主要是按照业务流程把梳理代码顺序执行。当业务逻辑清晰,业务规模不大的情况下当然没问题。一旦业务规模庞大,逻辑不清楚,增加需求的时候,开发难度就会曾几何数递增。

面向对象开发中会把对应的事物都抽象成对象的概念,这样子我们在增加对应业务时只需要增加相应的对象类,而无需改变其他类就能达到我们的目的。

面向对象的三大基本特征是:封装继承多态。随着这篇文章的深入我们会一一深入学习这三个基本特征。

类与对象

在面向过程的编程方式中,如果我们要定义一些人名与欢迎语,并用方法执行,我们通常会写出以下代码。

xiaoming = {'name': '小明', 'hello': '你好'}
kelly = {'name': 'Kelly', 'hello': 'hello'}

def say_hello(human):
    print('{}:{}'.format(human['name'], human['hello']))

say_hello(xiaoming)
say_hello(kelly)
小明:你好呀
Kelly:hello

在人物数量不多时,代码结构还是很清晰的,可是一旦人物数量变多了,就是一个灾难。

xiaoming = {'name': '小明', 'hello': '你好'}
kelly = {'name': 'Kelly', 'hello': 'hello'}
xiaoli = {'name': '小李', 'hello': '雷猴啊'}
lotus = {'name': 'Lotus', 'helo': 'bonjour'}

def say_hello(human):
    print('{}:{}'.format(human['name'], human['hello']))

say_hello(xiaoming)
say_hello(kelly)
say_hello(xiaoli)
say_hello(lotus)
小明:你好
Kelly:hello
小李:雷猴啊
Traceback (most recent call last):
  File ".\first.py", line 14, in <module>
    say_hello(lotus)
  File ".\first.py", line 8, in say_hello
    print('{}:{}'.format(human['name'], human['hello']))
KeyError: 'hello'

大量的重复代码已经是个灾难,一不小心写错一个字母还引起了程序的崩溃。此时如果再加上 say_bye等几个方法,代码结构将会更加混乱不堪。

接着我们用面向对象的思维来改造我们的代码,类的定义一定程度上就对应着基本特征封装

class Human:
    # 定义了一个类

    def __init__(self, name, hello):
        # 构造函数
        self.name = name
        self.hello = hello

    def say_hello(self):
        # 类的方法
        print('{}:{}'.format(self.name, self.hello))

# 实例化我们的对象
xiaoming = Human('小明', '你好呀')
kelly = Human('Kelly', 'hello')
xiaoli = Human('小李', '雷猴啊')
lotus = Human('Lotus', 'bonjour')


xiaoming.say_hello()
kelly.say_hello()
xiaoli.say_hello()
lotus.say_hello()
小明:你好呀
Kelly:hello
小李:雷猴啊
Lotus:bonjour

(Class)是用来描述具有相同属性(Attribute)和方法(Method)对象的集合。对象(Object)是(Class)的具体实例。

此时的 Human 就是我们定义的类,从面向对象编程来说就相当于封装了一个类。然后通过Human这个类创造了两个对象 xiaomingkelly

python 中我们通过 class 来定义一个类,指定了类应当有的属性与方法。将相关的属性(Attribute)与方法(Method)封装到对象中,是来源于现实世界的一个设计思想。因为现实世界中的任何物体都有其对应的属性(组成部分)与方法(功能作用)。

比如一个手机,它有屏幕、处理器、摄像头等等属性,有打电话、玩游戏、看电影等等方法。一个人,有姓名、身高、学历等等属性,有交流、工作、学习等等方法。

回到我们的代码,__init__ 是一个构造函数,它要求在实例化的时候传入 namehello 两个参数,并将其保存为自身的属性。这里的 self 代表着一个自身的实例对象。

xiaoming = Human('小明', '你好呀') 意味着实例化了一个 Human 对象。接着我们调用 xiaoming.say_hello() 便完成了对其对象方法的调用。

私有属性与私有方法 Private attribute and private method

一个生活中的人,你自然不会在脑门上看到他的名字,与说你好的方式。这些信息只会在你们初次交流时才可以得到。那我们就可以将 namehello 包括 say_hello 设置为私有属性与私有方法。而只对外面暴露一个 contact 方法。

class Human:
    # 定义了一个类

    def __init__(self, name, hello):
        # 构造函数
        self.__name = name
        self.__hello = hello

    def __say_hello(self):
        # 类的方法
        print('{}:{}'.format(self.__name, self.__hello))

# 实例化我们的对象
xiaoming = Human('小明', '你好呀')
xiaoming.__say_hello() # 这行代码会报错
Traceback (most recent call last):
  File ".\first.py", line 16, in <module>
    xiaoming.__say_hello()  # 这行代码会报错
AttributeError: 'Human' object has no attribute '__say_hello'

python 中通过在属性名与方法名前加上 __ 双下划线来定义私有成员,此时的私有成员便不再能从类外访问。我们可以再对其增加一个 contact 方法供外部调用。

class Human:
    # 定义了一个类

    def __init__(self, name, hello):
        # 构造函数
        self.__name = name
        self.__hello = hello

    def __say_hello(self):
        # 类的方法
        print('{}:{}'.format(self.__name, self.__hello))

    def contact(self):
        self.__say_hello()

# 实例化我们的对象
xiaoming = Human('小明', '你好呀')
xiaoming.contact()
小明:你好呀

以上体现了封装的理念,只对外暴露需要的接口,而对不必要的信息进行隐藏。

类的继承

通过继承,我们可以重用父类的属性与方法。

class Human:
    def __init__(self, name, hello):
        self.__name = name
        self.__hello = hello

    def __say_hello(self):
        print('{}:{}'.format(self.__name, self.__hello))

    def contact(self):
        self.__say_hello()

class Programmer(Human):
    def code(self):
        print('我正在快乐地编程')

class Fireman(Human):
    def outfire(self):
        print('我正在跟火灾作斗争')

xiaoming = Programmer('小明', '你好呀')
xiaoli = Fireman('小李', '雷猴啊')

xiaoming.contact()
xiaoming.code()

xiaoli.contact()
xiaoli.outfire()
小明:你好呀
我正在快乐地编程
小李:雷猴啊
我正在跟火灾作斗争

此时我们便已经定义了两个子类 ProgrammerFireman 都继承自 Human 类,既重用了父辈的方法与属性,又各自新增了自己独有的方法。

多态

通过多态,我们可以在不同的子类对同一个方法实现不同功能。

class Human:
    # 定义了一个类

    def __init__(self, name, hello):
        # 构造函数
        self.__name = name
        self.__hello = hello

    def __say_hello(self):
        # 类的方法
        print('{}:{}'.format(self.__name, self.__hello))

    def contact(self):
        self.__say_hello()

    def work():
        raise NotImplementedError()

class Programmer(Human):
    def contact(self):
        super().contact() # 调用父类的方法
        print('我是一个快乐的程序员')

    def work(self):
        print('我正在快乐地编程')

class Fireman(Human):
    def contact(self):
        super().contact() # 调用父类的方法
        print('我是一个消防员,遇火灾联系我!')

    def work(self):
        print('我正在跟火灾作斗争')

xiaoming = Programmer('小明', '你好呀')
xiaoli = Fireman('小李', '雷猴啊')

xiaoming.contact()
xiaoming.work()

xiaoli.contact()
xiaoli.work()
小明:你好呀
我是一个快乐的程序员
我正在快乐地编程
小李:雷猴啊
我是一个消防员,遇火灾联系我!
我正在跟火灾作斗争

我们重写了其中的 contact 方法。通过 super() 获取到了父类并调用了其 contact 方法。

我们同样在父类中定义了 work 方法,raise NotImplementedError() 的意思是在调用 work 方法前必须对其进行重写,不然会报错。 于是我们在子类中根据 Programmer 类与 Fireman 类的特性,分别对 work 方法进行了重写。

这时多态的效果便已达到,针对同样继承的父类,不同的子类在同一方法上进行了不同的实现

带来的好处是显而易见的,针对所有继承自 Human 类的子类,我们都可以直接调用他的 work 方法让他去工作,而无需关心内部的实现。

实战

1.定义一个女(男)朋友类,类至少包含姓名国籍 两个属性, 问好约会两个方法。并将其实例化。

开放式问题,答案不唯一。

解析

  1. 定义一个女(男)朋友类,类至少包含姓名国籍 两个属性, 问好约会两个方法。并将其实例化。

开放式问题,答案不唯一。

class Friend:
    def __init__(self, name, country):
        self.name = name
        self.country = country

    def say_hello(self):
        print('%s向你问好' % self.name)

    def date(self):
        print('约会吗?我在%s等你' % self.country)

xiaofang = Friend(name='小芳', country='日本')
xiaoming = Friend(name='小明', country='美国')

xiaofang.say_hello()
xiaofang.date()

xiaoming.say_hello()
xiaoming.date()
小芳向你问好
约会吗?我在日本等你
小明向你问好
约会吗?我在美国等你
点击查看

评论

登录参与讨论

小和

2022-09-04

回复

共 1 条
  • 1
前往
  • 1