面向对象

一、面向对象的设计

我们设计的软件很对时候是对现实世界的一个模拟系统,比如我们要设计一个非常简单的文字游戏,如下:

一个动物园有十个房间,里面有两种动物,老虎、羊。老虎的体重是200斤,羊体重100斤。游戏开始之前,在10个房间里面,随机放入老虎和羊。游戏这的目标是要把羊和老虎喂的越重越好。
游戏开始后,系统随机给出房间号。游戏者开始必须弄清楚,每个房间是老虎还是羊,方法是敲房间门,里面的动物都会叫,老虎叫声‘wow!!!’,
羊叫声是‘mie’。动物每叫一次体重减5斤。喂老虎应该输入单词meet,喂羊应该输入单词grass。喂对了体重加10斤。喂错了。体重减少10斤

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

  • 我们以前做的事情距离现实世界比较远,所以设计一般是面向过程的设计,一般是一些代码语句的顺序执行,中间加上一些循环,判断语句。最多是为了可读性,把相同功能点的代码组织成函数,甚至把大块函数通过切割成小块函数来降低系统的复杂度;
  • 之前,那样做就可以解决那时候的问题了,但是现在的问题,是个典型的现实世界的模拟的系统,这时候用面向过程的设计思路来设计系统也是不行。只是这个建模的思路通常会比较费解,这时候用面向对象的设计(OOD)是最自然最容易理解。

所谓面向对象的设计(OOD),简单说,就是分析要解决的现实世界的问题,抽象出各种对象,并通过设计对象之间的关系和交互行为来设计系统,因为现实世界,其实就是各种对象和对象之间的关系、交互行为。所以这种设计方式,更容易被人所理解和接受

二、类和实例

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

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

2.1.首先,寻找要设计的对象

上面的案例中对象有:老虎、羊、房间、动物园、人

2.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!!')

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

2.3.实例是类的具体化存在

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

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

老虎这个类,描述了:

  • 在这个系统里面共有的属性:体重、名称
  • 行为有叫、吃食物

每个具体的老虎,是老虎这个类的 实例

  • 房间中老虎

类的实例的关系,有点像 模具和根据模具做的东西一样

三、面向对象的语言 vs 面向对象的设计

  • 面向对象的设计:是一种设计思路
  • 面向对象的语言:提供对面向对象设计的语言支持 Python java js
  • 非面向对象的语言也能实现面向对象的设计思路

在任何语言中都可以采用这个面向对象的设计思想进行编码

四、属性

4.1.对象属性

我们刚说到的老虎这类对象,他们在这个系统里面共有的属性,体重和名称。可是我们在上面的类定义的例子里面只定义了名称和属性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,注意静态属性(类属性classname),可以通过实例进行访问,也可以通过类名进行访问比如Tiger.classname,而实例属性,只能通过实例进行访问,不能通过类进行访问,比如这样就会报错

Tiger.weight

想一想为什么呢?

class Tiger():
    #静态属性 也称之为 类属性
    tiger_name = '老虎'

    #__init__下面定义实例属性实例属性。
    def __init__(self, weight):
        #体重 由于每一个老虎都是不一样的,所以定义为实例属性
        self.weight = weight

if __name__ == '__main__':
    #根据类创建实例,类的实例化,一旦在初始化方法init中定义了一个属性,如果要传入值,则在实例化的后面括号中传入值
    t1 = Tiger(200)
    #可以通过实例 访问属性?
    print(t1.weight) #访问实例属性
    print(t1.tiger_name) #访问静态属性

    print("*****************************")
    t2 = Tiger(500)
    print(t2.weight)
    print(t2.tiger_name)

    print("*****************************")
    #访问静态属性也就是类属性的时候,可以不通过实例本身访问,直接通过  类名.属性名  的方式访问
    print(Tiger.tiger_name)

4.2.类属性

该类的所有实例共享的属性

4.3.实例属性

实例属性定义在 初始化方法 __init__里面,是每个实例所独有的属性,例如老虎的体重

相关概念说明:

  • python里面定义类的时候使用的关键字class,
  • 类名的命名规则和变量类似,只不过建议首字母大小
  • 静态属性:所有的实例相同的属性,直接定义在类里面就可以
  • 实例属性:每一个实例所独有的属性,实例属性要定义在初始化方法init里面,
  • 初始化方法init,在类的实例化的时候就会自动调用执行
  • 实例属性必须通过实例化对象本身来访问,而静态属性则既可以通过实例化对象本身来访问,也可以通过类名来访问

五、面向对象方法

5.1.对象方法

1、对象的方法就是描述对象行为的

  • 通过定义在类里面的函数
  • 初始化方法__init__函数

2、类的方法包括

  • 实例方法:每个具体实例相关的方法
  • 静态方法:共有的方法,每个具体实例都是相同的
  • 类方法:接收 cls,用于访问和修改类属性,适合定义与类相关的方法,不涉及实例数据。

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

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这样的参数,同样的静态方法描述的是类所共有的方法,而实例方法则实例的行为可能会不同。静态方法里面无法访问实例属性和实例方法。想一想为什么?

5.2.实例方法

1、与每个具体实例相关的方法

  • 如果代码中的实例方法不访问任何实例属性,一般建议实现为静态方法

2、初始化方法就是一个实例方法
3、一般实例方法都要访问self

5.3.静态方法

静态方法:每一个实例相同的方法(行为)称之为静态方法,静态方法可以访问静态属性,但是不能访问实例属性

静态方法是用 @staticmethod 装饰器定义的方法。它们不会接收 selfcls 参数,因此无法访问或修改实例或类的属性。静态方法更像是与类相关的普通函数,不依赖于类的实例或类本身

案例一:

class Tiger():
    #静态属性
    tigername = "老虎"
    def __init__(self, w1=200):
        #实例属性
        self.w = w1
        print("*******************")
 
    #实例方法,喂食操作
    def feed(self, food):
        #判断食物的类型
        if food == 'meet':
            self.w += 10
        elif food == 'grass':
            self.w -= 10
 
    #定义老虎的叫声
    def rore(self):
        a = Tiger.tigername
        print(f'{a}wow!!!')
        self.w -= 5
 
    @staticmethod
    def jump():  #静态方法
        a = Tiger.tigername
        print(f"{a}跳跃高度3米")
 
    #类方法 @classmethod使用这个来修饰
    @classmethod
    def a(cls):
        print("in a")
 
#实例化出来两只老虎
t1 = Tiger()
# t2 = Tiger()
 
# #调用实例方法,实例.方法名
print(t1.rore())
# #调用实例属性
# print(t1.w)
#
# #调用静态方法
# print(t1.jump())
#
# #通过类名 调用静态方法
# print(Tiger.jump())
 
#访问类方法
# print(t1.a())
# print(Tiger.a())

 

案例二:

class MyClass:
    @staticmethod
    def static_method():
        print("Static method called")

MyClass.static_method()  # 输出: Static method called

特点:

  • 不依赖于类实例或类本身。
  • 适用于类级别的操作,而不涉及实例数据。

5.4.类方法

类方法是用 @classmethod 装饰器定义的方法。它们接收类对象(cls)作为第一个参数,而不是实例对象。类方法可以访问和修改类的属性,但不能直接访问实例的属性。

class MyClass:
    class_variable = "Class variable"

    @classmethod
    def class_method(cls):
        print(f"Class method called with class variable: {cls.class_variable}")

MyClass.class_method()  # 输出: Class method called with class variable: Class variable

5.5.总结

实例方式:每一个实例所独有的方法(也就是行为),带self的就是实例方法,初始化方法init就是实例方法,实例方法既可以操作实例属性也可以操作静态属性
静态方法:每一个实例相同的方法(行为)称之为静态方法,静态方法可以访问静态属性,但是不能访问实例属性
类方法:这类所独有的一些方法,类方法可以访问静态属性
注意:

  • 如果方法中操作了实例属性,就定义为实例方法
  • @staticmethod是一个装饰器,指明了他下面的方法是一个静态方法
  • 实例方法只能通过实例化对象来调用,静态方法可以通过实例对象来调用,也可以通过类名来调用

六、对象的组合

6.1.什么是对象的组合?

现实世界里面的对象可以是大对象里面有小对象,例如大象可以有脚、眼睛、耳朵、这些对象,在面向对象里面,对象也是可以互相组合的,比如上面的例子,我们游戏里面需要一个房间对象,里面会有老虎或者羊

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()

6.2.代码实现

游戏里面房间对象里可能是任何老虎或者羊,房间对象的房间号开始也是不确定的:

class Room:
    def __init__(self,num,animal):
    self.num = num
    self.animal = animal
 
class Tiger():
    #静态属性
    tigername = "老虎"
    def __init__(self, w1=200):
        #实例属性
        self.w = w1
        print("*******************")
 
    #实例方法,喂食操作
    def feed(self, food):
        #判断食物的类型
        if food == 'meet':
            self.w += 10
        elif food == 'grass':
            self.w -= 10
 
    #定义老虎的叫声
    def rore(self):
        a = Tiger.tigername
        print(f'{a}wow!!!')
        self.w -= 5
 
class Sheep():
    sheepname = ''
    def __init__(self, w=100):
        #定义实例属性 体重
        self.w = w
 
    #定义动物的叫声
    def rore(self):
        print("miemiemie")
        self.w -= 5
 
    def feed(self,food):
        #判断喂食的食物
        if food == 'grass':
            print("喂对了,恭喜你")
            self.w += 10
        elif food == 'meet':
            print("喂错了")
            self.w -= 10
 
#房间类
class Room():
    #对象的组合其实就是把其他的对象当做参数传递进来
    def __init__(self, num, animal):
        #定义两个实例属性,房间号 、动物
        self.num = num
        self.animal = animal
 
#实例化出来一个羊
s1 = Sheep()
 
#实例化房间
r1 = Room(1, s1)
#r1.animall 其实就是传入的s1,既然是实例化的s1,那么就可以访问他的实例属性体重
 
#访问实例方法
r1.animal.rore()
print(r1.animal.w)
 
#访问房间动物的 静态属性、调用静态方法
print(r1.animal.sheepname)

七、面向对象的三大特征

7.1.对象的继承

上个小节说的对象可以由别的对象组成,这完美的比喻了现实世界的对象之间的一种包含关系,还有一种现实世界的情况,用对象组合比较难模拟。比如上面说的老虎这类对象。如果我们的游戏稍微扩展一下:

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

那时候,就要用到类对象的继承这种特性。继承就是现实世界中大类和小类的关系。比如桌子===》电脑桌 ,恐龙===》霸王龙。电脑桌就是桌子的一种,我们可以说一张电脑桌就是一张桌子,但是反过来,我们不能说一张桌子就是一张电脑桌:

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

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

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

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

来看一个具体的例子。还是那个老虎的例子

class Tiger(object):
    classname = 'tiger'

    def __init__(self, weight=200)
        self.weight = weight

    def roar(self):
        """
        吼叫
        :return:
        """
        print('wow!!!')
        self.weight -= 5

    def feed(self, food):
        """
        喂食操作
        :param food:
        :return:
        """
        if food == 'meat':
            self.weight += 10
            print('good,weight+10')
        else:
            self.weight -= 10
            print('bad,weight-10')


class NeTiger(Tiger):
    """
    东北虎,继承老虎类
    继承的时候要在子类的类名后面的括号加上父类的名字,并且要在子类的初始化方法init调用父类的初始化方法init
    子类继承了父类,就拥有了父类的一切属性和行为
    """
    color = 'yellow while'

    def __init__(self, weight=200):
        Tiger.__init__(self, weight)

    @staticmethod
    def jump():
        """
        跳跃
        :return: 
        """
        print('3 meters high')


class ScTiger(Tiger):
    """
    子类华南虎,继承父类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_里面一般要调用父类的初始化函数
  • 当然我们还可以根据实际的需要,设计出子类的子类,子类的子类的子类

7.2.封装

以类的形式封装完成代码的开发

7.3.多态

同一个方法的调用,在不容类型的对象上会表现出不同的特性,称之为多态。

多态通过重定义(或称为方法重写)实现,它使得子类可以提供父类方法的具体实现。这样,你可以用相同的方式调用方法,但实际执行的行为会根据对象的具体类型而有所不同:

  • 子类的属性和行为跟父类不一样
  • 子类实例 调用这些属性、方法 会使用新的定义

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

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和方法被重新定义了。这时候该类实例调用他们,就会使用新的定义 。

7.4.抽象

往往提到面向对象,我们都会说面向对象有三大特征:继续、封装、多态,但是有时候也有人说面向对象有四大特征:继续、封装、多态、抽象

抽象(就是抽象成类的形式,这个一般很少提及)

八、游戏完整代码

#定义 类 使用 class 关键字
class Tiger():
    #静态属性
    classname = '老虎'
    #实例属性
    def __init__(self,weight= 200):
        #self代表实例对象本身
        self.weight = weight
    #老虎叫声,实例方法
    def jiao(self):
        print("wow!!!")
        self.weight -= 5
    #喂食
    def eat(self,tt):
        if tt == 'meet':
            self.weight += 10
        elif tt == 'grass':
            self.weight -= 10
class Sheep():
    #静态属性
    classname = '喜羊羊'
    #实例属性
    def __init__(self,weight= 100):
        #self代表实例对象本身
        self.weight = weight
    #老虎叫声,实例方法
    def jiao(self):
        print("mie!!!")
        self.weight -= 5
    #喂食
    def eat(self,tt):
        if tt == 'meet':
            self.weight -= 10
        elif tt == 'grass':
            self.weight += 10
class Room():
    def __init__(self,room_num,animal):
        self.room_num = room_num
        self.animal = animal

from random import randint
rooms = []
#实例化10个房间对象
for one in range(0,10):
    if randint(0,1) == 1:
        ani = Tiger()
    else:
        ani = Sheep()
    #对象的组合
    aaa = Room(one,ani)
    #实例化的10个房间,添加到列表中
    rooms.append(aaa)

import time
ss = time.time()
while True:
    sss = time.time()
    if sss - ss > 20:
        print("游戏结束")
        for idx,room in enumerate(rooms):
            print("房间:%s" %(idx+1),room.animal.classname,room.animal.weight)
            #print(f"{idx+ 1}的动物为{room.animal.classname}体重是{room.animal.wieht}")
        break
    #随机出现1到10作为列表下标
    room_no = randint(1, 10)
    room = rooms[room_no-1]
    mm = input("我们来到了房间:%s,要敲门吗?[y/n]" %room_no)
    if mm == 'y':
        room.animal.jiao()
    yy = input("请喂食:")
    room.animal.eat(yy)
posted @ 2019-08-01 16:00  酒剑仙*  阅读(780)  评论(0)    收藏  举报