类和对象
封装、继承、多态
继承和多态
子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。
单一职责原则
开闭原则
依赖倒转原则
里氏替换原则
接口隔离原则
合成聚合复用原则
—-“迪米特法则”
函数:
名字、参数、默认值、可变参数、关键字参数、命名关键字参数
返回值
装饰器
之前有说过Python中属性和方法的访问权限问题。如果命名是以下划线为开头的,那么其实是隐喻了这是一个隐藏变量,外界想要访问需要有一个包装器加以调用和访问。
当我们不知道一个类是否能被创造出来时,就需要有一个静态方法来定义,比如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。
from math import sqrt
class Triangle(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
@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 sqrt(half * (half - self._a) *
(half - self._b) * (half - self._c))
def main():
a, b, c = 3, 4, 5
# 静态方法和类方法都是通过给类发消息来调用的
if Triangle.is_valid(a, b, c):
t = Triangle(a, b, c)
print(t.perimeter())
# 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
# print(Triangle.perimeter(t))
print(t.area())
# print(Triangle.area(t))
else:
print('无法构成三角形.')
if __name__ == '__main__':
main()
类和类之间的关系有三种:is-a、has-a和use-a关系。
is-a关系也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。
has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。
use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。
有一个国际通用的类图连接标准,大致为一下这几种:
——————▷ 继承关系
—— —— —— ——> 依赖关系
——————◇ 强关联(聚合关系)
—————— 关联关系
在老师的指导下,写了一些小程序来综合的体会一下面向对象的编程过程。
下面是奥特曼打小怪兽的小程序
奥特曼是一个对象,定义有名字,血量和魔法量,动作为攻击,大招和魔法攻击。怪兽是一个对象,有名字和血量,动作为普通攻击。
from random import randint
class Ultraman(object):
def __init__(self, name, hp, mp):
self._name = name
self._hp = hp
self._mp = mp
@property
def name(self):
return self._name
@property
def hp(self):
return self._hp
@hp.setter
def hp(self, hp):
self._hp = hp if hp >= 0 else 0
def attack(self, monster):
monster.hp -= randint(15, 25)
def huge_attack(self, monster):
if self.mp >= 50:
self.mp -= 50
injury = monster.hp * 3 // 4
injury = injury if injury >= 50 else 50
monster.hp -= injury
else:
self.attack(monster)
def magic_attack(self, monsters):
if self._mp >= 20:
self._mp -= 20
for monster in monsters:
monster.hp -= randint(10, 15)
def __str__(self):
return '%s奥特曼\n' % self._name + \
'生命值:%d\n' % self._hp + \
'魔法值:%d\n' % self._mp
class Monster(object):
def __init__(self, name, hp):
self._name = name
self._hp = hp
@property
def name(self):
return self._name
@property
def hp(self):
return self._hp
@hp.setter
def hp(self, hp):
self._hp = hp if hp >= 0 else 0
def attack(self, ultraman):
ultraman.hp -= randint(10, 20)
def __str__(self):
return '%s小怪兽\n' % self._name + \
'生命值:%d\n' % self._hp
def main():
u = Ultraman('王大锤', 1000, 200)
print(u)
m = Monster('鼠零', 500)
print(m)
attack_round = 1
while u.hp > 0 and m.hp > 0:
print('====第%d回合====' % attack_round)
u.attack(m)
if m.hp > 0:
m.attack(u)
print(u)
print(m)
attack_round += 1
if u.hp > 0:
print('%s奥特曼胜利!' % u.name )
else:
print('%s小怪兽胜利!' % m.name )
if __name__ == '__main__':
main()
大球吃小球的小程序
在pygame框架下,构建一个窗口,使用循环让窗口保持住,之后定义鼠标事件,在鼠标按下左键时,生成一个随机大小随机颜色的球,随机方向逃逸,碰到上边界就往下,碰到下边界就往上,同理碰到左边界往右,碰到右边界就往左边逃逸。之后再定义,两球碰到时,大的球会把小球吃掉,同时随机增长大小
from random import randint
from math import sqrt
import pygame
class Ball(object):
"""球的类"""
def __init__(self, center, color, radius, speed):
"""
球的初始化方法
:param center: 求的位置在哪
:param color: 球的颜色
:param radius: 球的半径
:param speed: 球的速度
"""
self._center = center
self._color = color
self._radius = radius
self._speed = speed
def move(self):
"""
球的移动
:return : None
"""
x, y = self._center[0], self._center[1] # 获取球当前的x, y坐标
sx, sy = self._speed[0], self._speed[1] # 获取当前球的速度
if x + self._radius > 800: # 如果球在右边框了
x -= int(self._radius/4)+abs(sx) # 大幅增加一个反向速度
sx = -sx
elif x - self._radius < 0: # 如果球在左边框了
x += int(self._radius/4)+abs(sx)
sx = -sx
elif y + self._radius > 600: # 如果球在下边框了
y -= int(self._radius/4)+abs(sy)
sy = -sy
elif y - self._radius < 0: # 如果球在上边框了
y += int(self._radius/4)+abs(sy)
sy = -sy
else:
x, y = sx + x, sy + y # 如果球在中间,正常的增减速度
self._center = (x, y) # 重置移动后的位置
self._speed = sx, sy # 速度重新赋值
@property
def center(self):
"""
装饰器修饰后的get方法
:return: x,y的坐标元组
"""
return self._center
@property
def radius(self):
return self._radius
def eat(self, other):
"""
球吃的动作,主动,而非被动
:param other: 被吃的球
:return: 布尔值,是否被吃掉了
"""
# 计算两球之间的距离
distance = sqrt((self._center[0] - other.center[0]) ** 2 +
(self._center[1] - other.center[1]) ** 2)
if distance < self._radius + other.radius and self._radius > other.radius:
self._radius = int(sqrt(other.radius ** 2 + self._radius ** 2)) # 赋予吃了球后的新半径
return True # 吃球成功
return False # 吃球失败
def draw(self, screen):
center_pos = (self._center[0], self._center[1])
pygame.draw.circle(screen, self._color, center_pos, self._radius)
def draw_ball():
"""
画一个球
:return:
"""
balls = []
pygame.init()
display = pygame.display
display.set_caption('大球吃小球')
clock = pygame.time.Clock()
screen = display.set_mode([800, 600])
is_running = True
while is_running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
is_running = False
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
color = random_color()
radius = randint(10, 50)
speed = [randint(-10, 10), randint(-10, 10)]
ball = Ball(event.pos, color, radius, speed)
balls.append(ball)
clock.tick(30)
for ball in balls:
ball.move()
# 通过双重for循环来判断,每个球是否能够被吃
for index, ball in enumerate(balls):
for j, other in enumerate(balls):
if index != j: # 不能自己吃自己
is_eat = ball.eat(other) # 是否吃成功
if is_eat:
balls.pop(j) # 成功后就将被吃的球拿出列表,达到消失的效果
refresh(screen, balls)
pygame.quit()
def refresh(screen, balls):
"""
刷新游戏页面
:param screen: 需要刷新的布局容器
:param balls: 装球的列表
:return:
"""
bg_color = (255, 255, 255)
screen.fill(bg_color)
for ball in balls:
ball.draw(screen)
pygame.display.flip()
def random_color():
"""
随机生成颜色
:return: 返回随机后的元组
"""
red = randint(0, 255)
green = randint(0, 255)
blue = randint(0, 255)
return red, green, blue
def main():
draw_ball()
pass
if __name__ == '__main__':
main()