11.1面向对象的设计

我们设计的软件很对时候是对现实世界的一个模拟系统。

比如我们要设计一个非常简单的文字游戏。

一个动物园有十个房间,里面有两种动物,老虎、羊。老虎的体重是200斤,羊体重100斤。游戏开始之前,在10个房间里面,随机

放入老虎和羊。游戏这的目标是要把羊和老虎喂的越重越好。

游戏开始后,系统随机给出房间号。游戏者开始必须弄清楚,每个房间是老虎还是羊,方法是敲房间门,里面的动物都会叫,老虎叫声‘wow!!!’,

羊叫声是‘mie’。动物每叫一次体重减5斤。喂老虎应该输入单词meet,喂羊应该输入单词grass。喂对了体重加10斤。喂错了。体重减少10斤

 

游戏者需要强记每个房间的动物是什么,以便不需要敲门就可以得知里面的动物是什么,从而该喂什么。这样不断循环。游戏2分钟结束后。看看你喂的动物总体重多少

 

这个游戏怎么设计

我们以前做的事情距离现实世界比较远,所以设计一般是面向过程的设计,一般是一些代码语句的顺序执行,中间加上一些循环,判断语句。最多是为了可读性,把相同功能

点的代码组织成函数,甚至把大块函数通过切割成小块函数来降低系统的复杂度

 

之前,那样做就可以解决那时候的问题了,但是现在的问题,是个典型的现实世界的模拟的系统,这时候用面向过程的设计思路来设计系统也是不行。只是这个建模的思路通常会比较费解

,这时候用面向对象的设计(OOD)是最自然最容易理解

 

所谓面向对象的设计(OOD),简单说,就是分析要解决的现实世界的问题,抽象出各种对象,并通过设计对象之间的关系和交互行为来设计系统

 

因为现实世界,其实就是各种对象和对象之间的关系、交互行为。所以这种设计方式,更容易被人所理解和接受

11.2.类和实例

面向对象设计里面开始要做的事情,就是寻找要设计的系统里面的对象有哪些。比如上面说的游戏,很明显的对象有老虎、样、房间。对象找到了,接下来就是要确定对象的定义

 

对象的定义,就是要找到对象在这个系统里面的属性和行为。在面向对象的软件设计学里面,我们把对象和他的属性和行为定义叫类定义。类表示了这类对象所共有的行为

和属性

 

比如老虎这类对象,它们在这个系统里面共有的属性,我们基本上一下子就能想到有体重(缺省200斤)、名称(tiger).而行为呢,有叫,和吃东西

 

所有面向对象的语言,天然的提供了 对类的支持。python语言当然也是面向对象的语言,它可以如下定义一个类

class ClassName(object):
     'class documentation string'
      class_suite

对于上面的老虎 类,可以这样定义

class Tiger:
    'a tiger class,which is used in this game as tiger.'
    classname = 'tiger'
    def roar(self):
        print('Wow!!')

注意,其中,class name是属性,表示老虎这一类动物的统称叫tiger,叫roar是行为,用一个函数定义表示。对应类的属性和行为的定义,我们下面会详细说明

 

对象定义好了,只是定义好了,我们真正对对象进行使用的时候,必须进行实例化。也就是创建该对象的类的一个实例。用如下方法:

tiger1 = Tiger()
tiger2 = Tiger()
print(tiger1.classname)
print(tiger2.calssname)
tiger1.roar()

类和实例的关系,可以想象成,类就像一个模型,比如花盆的模型,而实例则是根据这个模型做出来的真正的事物,一个个的花盆

 

11.3.静态属性和实例属性

我们刚说到的老虎这类对象,他们在这个系统里面共有的属性,体重和名称。可是我们在上面的类定义的例子里面只定义了名称和属性classname ='tiger',

并没有定义体重这个属性

这是因为,这两个属性类型有些不一样。自习想想,名称tiger是所有老虎对象共同的属性,他们的名称叫老虎(tiger),而体重则每个老虎都不一样。虽然游戏开始的时候

缺省的体重是200斤,但随着游戏的进行,每个房间里面的老虎体重可能不同,而名称这个属性,不管游戏进行到什么时候都是一样的

所以,我们把某个类型所有的对象共有的属性称之为静态属性(或类属性)。而每个对象实例各自的属性称之为实例属性。静态属性的定义上面已经有了,实例属性的定义如下

class Tiger:
    classname = 'tiger'
    def _init_(self):
         self.weighe=200

其中,定义的_init_函数被称之为构造函数,这个函数在实例化的时候,解释器自动调用。实例属性就是在构造函数中定义的。构造函数主要是初始化用的,包括初始化实例属性的值

,甚至可以调用其他的函数执行初始化操作。如果我们没有定义构造函数,那么类被实例化的时候。就没有什么初始化操作

 

_init_函数里面有个参数self,这个其实就是我们实例化好了的对象本身,也就是类的实例对象

 

现在我们再看一下类实例化的操作

tiger1 = Tiger()

这个Tiger后面加的括号里面,没有什么参数。但是如果有参数的话,这些参数会传递给构造函数使用,所以构造函数定义的时候必须有相应的参数定义

class Tiger:
    classname = 'tiger'
    
    def __init__(self,weight=200)
          self.weighe = weight

如果构造函数这样定义,初始化的时候可以

tiger1 = Tiger(300)
print(tiger1.weight)

这样显示的体重是300

要注意静态属性,可以通过实例进行访问,也可以通过类进行访问,比如Tiger.classname而实例属性,只能通过实例进行访问,不能通过类进行访问,比如这样就会报错

Tiger.weight

想一想为什么呢?

11.4.静态方法和实例方法

我们再来看一下老虎对象的行为的定义。刚才说过了,老虎对象的行为的定义是用类里面的函数定义来实现的。表示对象的行为的函数,我们通常叫做对象的方法

class Tiger:
    classname = 'tiger'

    def __init__(self,weight=200):
        self.weight = weight
    
    def roar(self):
        print('wow!!!')
        self.weight -=5

上个小节,我们说过对象的属性,可以分为静态属性和实例属性

同样的对象的方法,也可以分为静态方法和实例方法

上面定义的roar方法里面也有个self参数,这是这个类的实例方法。必须实例化后才能调用

而静态方法这样定义

class Tiger:
    classname = 'tiger'
    
    def _init_(self):
        self.weight = 200
    
    def roar(self):
        print('Wow!!!')

    @staticmethod
    def roar2():
        print('w')

tiger1 = Tiger()

上面的roar2就是一个静态方法。它通过一个装饰器@staticmethod来定义。静态方法里面不需要传入self这样的参数

同样的静态方法描述的是类所共有的方法,而实例方法则实例的行为可能会不同。

静态方法里面无法访问实例属性和实例方法。想一想为什么?

11.5.对象的组合

现实世界里面的对象可以是大对象里面有小对象,例如大象可以有脚、眼睛、耳朵、这些对象,在面向对象里面,对象也是可以互相组合的

比如上面的例子,我们游戏里面需要一个房间对象,里面会有老虎或者羊

class Room:
    def __init__(self):
        self.num = 1
        self.animal = Tiger(300)

上面的定义,房间这个对象包含了老虎对象。还包含一个对象是房间号,固定为1

大家看房间对象由老虎对象和其他对象组成(这里是房间号,一个int对象)。这就是对大象的组合

 

而游戏里面房间对象里可能是任何老虎或者羊,房间对象的房间号开始也是不确定的。可以这样定义

 ** 如何获取随机数

from random import randint
print(randint(0,1))
print(randint(1,10))

** 如何获取当前系统时间

import time
curTime = time.time()

11.6.对象的继承

上个小节说的对象可以由别的对象组成,这完美的比喻了现实世界的对象之间的一种包含关系

还有一种现实世界的情况,用对象组合比较难模拟。比如上面说的老虎这类对象。如果我们的游戏稍微扩展一下

老虎里面还分两种,东北虎和华南虎。东北虎跳起来有3米高,华南虎跳起来2米高。其他行为和之前的设计一样

 

那时候,就要用到OOP里面类对象的继承这种特性。继承就是现实世界中大类和小类的关系。比如桌子-》电脑桌 

恐龙-》霸王龙。电脑桌就是桌子的一种,我们可以说一张电脑桌就是一张桌子,但是反过来,我们不能说一张桌子就是一张电脑桌

 

组合是一种有一个的关系。A对象有一个B对象的关系

继承是一种是一个的关系。A对象是一个B对象的关系

 

在python里面类对象的继承关系是这样定义的

class SubClassName(ParentClass1[ParentClass2,...]):
    'optional class documentation string'
    class_suite

括号里面的是被继承的类,叫做父类(或者基类),SubClassName是继承类,叫做子类,注意父类可以有多个,比如中国学生这个类,它既可以有父类中国人,也可以有父类学生,它可以同时拥有两个父类所有共同的

特性

好了,我们来看一个具体的例子。还是那个老虎的例子

class Tigr(object):
    classname = 'tiger'
    
    def __init__(self,weight=200)
        self.weight = weight
    
    def roar(self):
        print('wow!!!')
        self.weight -= 5
    
    def feed(self,food):
        if food == 'meat':
            self.weight += 10
            print('good,weight+10')
        else:
            self.weight -= 10
            print('bad,weight-10')

class NeTiger(Tiger):
    color = 'yellow while'
    
    def __init__(self,weight=200)
        Tiger._init_(self,weight)
    
    @staticmethod
    def jump():
        print('3 meters high')

class ScTiger(Tiger):
    color = 'brown black'
    def __init__(self,weight=200)
        Tiger._init_(self,weight)
    
    @staticmethod
    def jump():
        print('2 meters high')

我们看到,NeTiger和ScTiger都是Tiger的子类。这里他们都拥有Tiger的一切属性和方法。同时他们还多出了自己增加的属性(color)和方法(jump)

要注意的还有,子类的初始化函数_init_里面一般要调用父类的初始化函数

当然我们还可以根据实际的需要,设计出子类的子类,子类的子类的子类

有的时候,我们还需要让子类的属性和行为和父类有些不一样。建设我们需要重新定义东北虎的声音,和它的名称。可以这样

class Netiger(Tiger):
    color = 'yellow white'
    calssname = 'northeast tiger'
    
    def __init__(self,weight=200):
        Tiger._init_(self,weight)
    
    def roar(self):
        print('wow!!! wow!!!  wow!!!')
        self.weight -= 5

    @staticmethod
    def jump()
        print('3 meters high')

大家注意,这里属性classname和方法被重新定义了。这时候该类实例调用他们,就会使用新的定义