面向对象与面向过程
- 面向过程:根据业务逻辑从上到下写代码。
- 面向对象:将变量与函数绑定到一起,分类进行封装,每个程序只要负责分配给自己的分类,这样能够更快速的开发程序,减少了重复代码。
面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程的思路是将数据与函数按照执行的逻辑顺序组织在一起,数据与函数分开考虑,面向过程基本是由函数组成的。
面向过程编程
面向过程编程的关注点在于怎么做
- 把完成某一个需求的 所有步骤 从头到尾 逐步实现
- 根据开发需求,将某些 功能独立 的代码 封装 成一个又一个 函数
- 最后完成的代码,就是顺序地调用 不同的函数
特点:
- 注重步骤与过程,不注重职责分工
- 如果需求复杂,代码会变得很复杂
- 开发复杂项目,没有固定的套路,开发难度很大!
类和对象
类
类是对一群具有相同特征或者行为 的事物的一个统称,是抽象的,不能直接使用
- 特征其实就是一个变量,在类里我们称之为属性。
- 行为其实就是一个函数,在类里我们称之为方法。
- 类其实就是由 属性 和 方法 组成的一个抽象概念。
类就相当于制造飞机时的图纸,是一个模板。这个模板只规定了飞机的某些特征(例如大小,颜色,型号等等)和行为(例如起飞,降落,飞行等等),它并不是一个具体的飞机,而是对飞机的一个抽象概念。它出现的目的,是为了让我们的创建飞机对象。
对象
对象是由类创建出来的一个具体存在,可以直接使用。由哪一个类创建出来的 对象,就拥有在哪一个类中定义的属性和方法。 对象 就相当于用图纸制造的飞机。在开发中,应该先有类,在类里定义好属性和行为,再根据类来创建对象。
类和对象的关系
- 类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象。
- 使用同一个类,能够创建出很多对象。
- 类中定义了什么属性和方法,对象中就有什么属性和方法。
- 不同对象对应的属性值也会不同。
例如:定义了一个狗类,这个狗类有以下属性:
- 品种
- 颜色
- 性别
- 名字
现在根据这个类创建出了两条狗,这两条狗分别是 哈士奇、灰色、母、二哈 和 中华田园犬、黄色、公、旺财。我们发现,这两条狗都具有 品种、颜色、性别和名字这些属性,但是每条狗对应的属性值却不一样。
类的设计
在使用面相对象开发前,应该首先分析需求,确定一下,程序中需要包含哪些类!
在程序开发中,要设计一个类,通常需要满足一下三个要素:
- 类名 这类事物的名字,安照大驼峰命名法(每个单词的首字母大写)起名。
- 属性 这类事物具有什么样的特征。
- 方法 这类事物具有什么样的行为。
定义类名
名词提炼法:分析整个业务流程,出现的名词,通常就是找到的类。属性和方法的确定
- 对对象的特征描述,可以定义成属性
- 对象具有的行为(动词)可以定义成方法
面向对象基本语法
在Python中,对象几乎是无处不在的,我们可以使用dir内置函数来查看这个对象里的方法。
定义简单的类(只包含方法)
面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了!
定义类
在Python中要定义一个只包含方法的类,语法格式如下:
class 类名:def 方法1(self,参数列表):passdef 方法2(self,参数列表):pass
- 方法的定义格式和之前学习过的函数一样
- 方法里的第一个参数必须是self,大家暂时先记住,稍后介绍 self.
- 类名要遵守大驼峰命名法。
self的使用
class Cat:"""这是个猫类"""def eat(self):print("小猫在吃东西")def drink(self):print("小猫在喝水")tom = Cat() # 创建了一个Cat对象tom.eat()tom.drink()
给对象添加属性
python支持动态属性,当一个对象创建好了以后,直接使用 对象.属性名 = 属性值 就可以很方便的给对象添加一个属性。
tom = Cat()tom.name = 'Tom' # 可以直接给 tom 对象添加一个 name 属性
self的概念
哪个对象调用了方法,方法里的self指的就是谁。 通过 self.属性名 可以访问到这个对象的属性;通过 self.方法名() 可以调用这个对象的方法。
class Cat:def eat(self):print("%s爱吃鱼" %self.name)tom = Cat()tom.name = 'Tom' # 给 tom 对象添加了一个name属性tom.eat() # Tom爱吃鱼lazy_cat = Cat()lazy_cat.name = "大懒猫"lazy_cat.eat() # 大懒猫爱吃鱼
直接给对象添加属性的缺点
上述代码中,我们是先创建对象,然后再给对象添加 name 属性,但是这样做会有问题。
tom = Cat()tom.eat()tom.anme = "Tom"
程序运行时会报错:
AttributeError: 'Cat' object has no attribute 'name'错误提示:'Cat'对象没有 'name' 属性
在日常开发中,不推荐在类的外部直接给对象添加属性这种方式。对象应该具有哪些属性,我们应该封装在类的内部。
魔法方法
Python 里有一种方法,叫做魔法方法。Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,魔法方法在恰当的时候就会被激活,自动执行。 魔法方法的两个特点:
- 两侧各有两个下划线;
-
init方法
__init__()方法,在创建一个对象时默认被调用,不需要手动调用。在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对__init__方法进行改造。 ```python class Cat: “””这是一个猫类””” def init(self,name): # 重写了 init 魔法方法self.name = name
def eat(self):
return "%s爱吃鱼"%self.namedef drink(self):
return '%s爱喝水'%self.name“””
tom = Cat() TypeError: __init__() missing 1 required positional argument: 'name' 这种写法在运行时会直接报错!因为 __init__ 方法里要求在创建对象时,必须要传递 name 属性,如果不传入会直接报错!“””
tom = Cat(“Tom”) # 创建对象时,必须要指定name属性的值 tom.eat() # tom爱吃鱼
注意:
1. `__init__()`方法在创建对象时,会默认被调用,不需要手动的调用这个方法。
1. `__init__()`方法里的self参数,在创建对象时不需要传递参数,python解释器会把创建好的对象引用直接赋值给self
1. 在类的内部,可以使用self来使用属性和调用方法;在类的外部,需要使用对象名来使用属性和调用方法。
1. 如果有多个对象,每个对象的属性是各自保存的,都有各自独立的地址。
1. 方法是所有对象共享的,只占用一份内存空间,方法被调用时会通过self来判断是哪个对象调用了实例方法。
<a name="75585d88"></a>
### __del__方法
创建对象后,python解释器默认调用`__init__()`方法;<br />而当删除对象时,python解释器也会默认调用一个方法,这个方法为`__del__()`方法。
```python
class Student:
def __init__(self,name,score):
print('__init__方法被调用了')
self.name = name
self.score = score
def __del__(self):
print('__del__方法被调用了')
s = Student('lisi',95)
del s
input('请输入内容')
str方法
__str__方法返回对象的描述信息,使用print()函数打印对象时,其实调用的就是这个对象的__str__方法。
class Cat:
def __init__(self,name,color):
self.name = name
self.color = color
tom = Cat('Tom','white')
# 使用 print 方法打印对象时,会调用对象的 __str__ 方法,默认会打印类名和对象的地址名
print(tom) # <__main__.Cat object at 0x0000021BE3B9C940>
如果想要修改对象的输出的结果,可以重写 __str__ 方法。
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return '哈哈'
p = Person('张三',18)
print(p) # 哈哈 打印对象时,会自动调用对象的 __str__ 方法
一般情况下,我们在打印一个对象时,可能需要列出这个对象的所有属性。
class Student:
def __init__(self,name,score):
self.name = name
self.score = score
def __str__(self):
return '姓名是:{},成绩是{}分'.format(self.name,self.score)
s = Student('lisi',95)
print(s) # 姓名是:lisi,成绩是95分
repr方法
__repr__方法和__str__方法功能类似,都是用来修改一个对象的默认打印内容。
在打印一个对象时,如果没有重写__str__方法,它会自动来查找__repr__方法。
如果这两个方法都没有,会直接打印这个对象的内存地址。
使用__str__和__repr__方法,都会修改一个对象转换成为字符串的结果。
一般来说,__str__方法的结果更加在意可读性,而__repr__方法的结果更加在意正确性(例如:datetime模块里的datetime类)
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __repr__(self):
return 'helllo'
class Person:
def __repr__(self):
return 'hi'
def __str__(self):
return 'good'
s = Student('lisi', 95)
print(s) # hello
p = Person()
print(p) # good
call方法
对象后面加括号,触发执行。
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
内置属性
使用内置函数dir可以查看一个对象支持的所有属性和方法,Python中存在着很多的内置属性。
slots
Python中支持动态属性,可以直接通过语法直接给一个对象添加属性,代码更加的灵活。
但是在某些情况下,我们可能需要对属性进行控制,此时,就可使用__slots__实现。
class Person(object):
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
p = Person('张三', 18)
p.name = '李四'
# 对象p只能设置name和age属性,不能再动态添加属性
# p.height = 180 # 报错
doc
表示类的描述信息。
class Foo:
""" 描述类信息,这是用于看片的神奇 """
def func(self):
pass
print(Foo.__doc__)
#输出:类的描述信息
module
__module__ 表示当前操作的对象在那个模块
class Person(object):
def __init__(self):
self.name = 'laowang'
from test import Person
obj = Person()
print(obj.__module__) # 输出 test 即:输出模块
class
__class__ 表示当前操作的对象的类是什么。
class Person(object):
def __init__(self):
self.name = 'laowang'
from test import Person
obj = Person()
print(obj.__class__) # 输出 test.Person 即:输出类
dict
以字典的形式,显示对象所有的属性和方法。
class Province(object):
country = 'China'
def __init__(self, name, count):
self.name = name
self.count = count
def func(self, *args, **kwargs):
print('func')
# 获取类的属性,即:类属性、方法、
print(Province.__dict__) # 输出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}
obj1 = Province('山东', 10000)
# 获取 对象obj1 的属性
print(obj1.__dict__) # 输出:{'count': 10000, 'name': '山东'}
# 获取 对象obj2 的属性
obj2 = Province('山西', 20000)
print(obj2.__dict__) # 输出:{'count': 20000, 'name': '山西'}
字典式操作对象
__getitem__、___``_setitem__和__delitem__,这三个方法,是将对象当做字典一样进行操作。
class Foo(object):
def __getitem__(self, key):
print('__getitem__', key)
def __setitem__(self, key, value):
print('__setitem__', key, value)
def __delitem__(self, key):
print('__delitem__', key)
obj = Foo()
result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = 'laowang' # 自动触发执行 __setitem__
del obj['k1'] # 自动触发执行 __delitem__
实例属性和类属性
在面向对象开发中,使用类创建出来的实例是一个对象,那么,类是否是一个对象呢?
实例属性
通过类创建的对象被称为 实例对象,对象属性又称为实例属性,记录对象各自的数据,不同对象的同名实例属性,记录的数据各自独立,互不干扰。
class Person(object):
def __init__(self, name, age):
# 这里的 name 和 age 都属于是实例属性,每个实例在创建时,都有自己的属性
self.name = name
self.age = age
# 每创建一个对象,这个对象就有自己的name和age属性
p1 = Person('张三',18)
p2 = Person("李四",20)
类属性
类属性就是类对象所拥有的属性,它被该类的所有实例对象所共有,类属性可以通过类对象或者实例对象访问。
class Dog:
type = "狗" # 类属性
dog1 = Dog()
dog2 = Dog()
# 不管是dog1、dog2还是Dog类,都可以访问到type属性
print(Dog.type) # 结果:狗
print(dog1.type) # 结果:狗
print(dog2.type) # 结果:狗
使用场景:
- 类的实例记录的某项数据始终保持一致时,则定义类属性。
/实例属性要求每个对象为其单独开辟一份内存空间来记录数据,而类属性为全类所共有 ,仅占用一份内存,更加节省内存空间。
注意点:
尽量避免类属性和实例属性同名。如果有同名实例属性,实例对象会优先访问实例属性。
class Dog(object):
type = "狗" # 类属性
def __init__(self):
self.type = "dog" # 对象属性
# 创建对象
dog1 = Dog()
print(dog1.type) # 结果为 “dog” 类属性和实例属性同名,使用 实例对象 访问的是 实例属性
- 类属性只能通过类对象修改,不能通过实例对象修改
**
class Dog(object):
type = "狗" # 类属性
# 创建对象
dog1 = Dog()
dog1.type = "dog" # 使用 实例对象 创建了对象属性type
print(dog1.type) # 结果为 “dog” 类属性和实例属性同名,访问的是实例属性
print(Dog.type) # 结果为 "狗" 访问类属性
# 只有使用类名才能修改类属性
Dog.type = "土狗"
print(Dog.type) # 土狗
dog2 = Dog()
print(dog2.type) # 土狗
- 类属性也可以设置为 **私有**,前边添加两个下划线。 如:
class Dog(object):
count = 0 # 公有的类属性
__type = "狗" # 私有的类属性
print(Dog.count) # 正确
print(Dog.__type) # 错误,私有属性,外部无法访问。
私有属性和方法
在实际开发中,对象的某些属性或者方法可能只希望在对象的内部别使用,而不希望在外部被访问到,这时就可以定义私有属性和私有方法。
定义方法
在定义属性或方法时,在属性名或者方法名前增加两个下划线__,定义的就是私有属性或方法。
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
self.__money = 2000 # 使用 __ 修饰的属性,是私有属性
def __shopping(self, cost):
self.__money -= cost # __money 只能在对象内部使用
print('还剩下%d元'%self.__money)
def test(self):
self.__shopping(200) # __shopping 方法也只能在对象内部使用
p = Person('张三',18)
# print(p.__money) 这里会报错,不能直接访问对象内部的私有属性
p.test()
# p.__shopping() 这里会报错,__shopping 只能在对象内部使用,外部无法访问
访问私有属性和方法
私有属性不能直接使用,私有方法不能直接调用。但是,通过一些代码,我们也可以在外部访问一个对象的私有属性和方法。
直接访问
使用方式:在私有属性名或方法名前添加 _类名
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
self.__money = 2000
def __shopping(self, cost):
self.__money -= cost
p = Person('李四',20)
print(p._Person__money) # 使用对象名._类名__私有属性名 可以直接访问对象的私有属性
p._Person__shopping(100) # 使用对象名._类名__函数名 可以直接调用对象的私有方法
print(p._Person__money)
注意:在开发中,我们强烈 不建议 使用 对象名._类名__私有属性名 的方式来访问对象的私有属性!
定义方法访问私有变量
在实际开发中,如果对象的变量使用了__ 来修饰,就说明它是一个私有变量,不建议外部直接使用和修改。
如果硬要修改这个属性,可以使用定义get和set方法这种方式来实现。
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
self.__money = 2000 # __money 是私有变量,外部无法访问
def get_money(self): # 定义了get_money 方法,在这个方法里获取到 __money
return self.__money # 内部可以访问 __money 变量
def set_money(self,money): # 定义了set_money 方法,在这个方法里,可以修改 __money
self.__money = money
p = Person('王五',21)
# 外部通过调用 get_money 和 set_money 这两个公开方法获取和修改私有变量
print(p.get_money())
p.set_money(8000)
print(p.get_money())
进阶
类方法、静态方法
类方法
- 第一个形参是类对象的方法
- 需要用装饰器
@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数。
class Dog(object):
__type = "狗"
# 类方法,用classmethod来进行修饰
@classmethod
def get_type(cls):
return cls.__type
print(Dog.get_type())
使用场景:
- 当方法中 需要使用类对象 (如访问私有类属性等)时,定义类方法
- 类方法一般和类属性配合使用
静态方法
- 需要通过装饰器
@staticmethod来进行修饰,静态方法既不需要**传递类对象也不需要传递实例对象**(形参没有self/cls)。 - 静态方法 也能够通过 实例对象 和 类对象 去访问。
class Dog(object):
type = "狗"
def __init__(self):
name = None
# 静态方法
@staticmethod
def introduce(): # 静态方法不会自动传递实例对象和类对象
print("犬科哺乳动物,属于食肉目..")
dog1 = Dog()
Dog.introduce() # 可以用 实例对象 来调用 静态方法
dog1.introduce() # 可以用 类对象 来调用 静态方法
使用场景:
- 当方法中 既不需要使用实例对象(如实例对象,实例属性),也不需要使用类对象 (如类属性、类方法、创建实例等)时,定义静态方法
- 取消不需要的参数传递,有利于 减少不必要的内存占用和性能消耗
注意点:
- 类中定义了同名的方法时,调用方法会执行最后定义的方法
class Dog:
def demo_method(self):
print("对象方法")
@classmethod
def demo_method(cls):
print("类方法")
@staticmethod
def demo_method(): # 被最后定义
print("静态方法")
dog1 = Dog()
Dog.demo_method() # 结果: 静态方法
dog1.demo_method() # 结果: 静态方法
单例设计模式
new和init
class A(object):
def __init__(self):
print("这是 init 方法")
def __new__(cls):
print("这是 new 方法")
return object.__new__(cls)
A()
__new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供__new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以 返回 父类__new__出来的实例,或者直接是object的__new__出来的实例__init__有一个参数self,就是这个__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值单例设计模式
举个常见的单例模式例子,我们日常使用的电脑上都有一个回收站,在整个操作系统中,回收站只能有一个实例,整个系统都使用这个唯一的实例,而且回收站自行提供自己的实例。因此回收站是单例模式的应用。
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,单例模式是一种对象创建型模式。 ```python实例化一个单例
class Singleton(object): instance = None is_first = True
def new(cls, age, name):
if not cls.__instance: cls.__instance = object.__new__(cls) return cls.__instancedef init(self, age, name):
if self. __is_first: # 不会再创建第二个对象 self.age = age self.name = name Singleton. __is_first = False
a = Singleton(18, “张三”) b = Singleton(28, “张三”)
print(id(a)) print(id(b))
print(a.age) # 18 print(b.age) # 18
a.age = 19 print(b.age)
<a name="tdhzE"></a>
## 继承
继承是面向对象软件设计中的一个概念,与多态、封装共为面向对象的三个基本特征。
继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。
- 在程序中,继承描述的是多个类之间的所属关系。
- 如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里。
- 那么类A就是基类,也叫做父类;类B就是派生类,也叫做子类。
- 传递性:子类拥有父类以及父类的父类中封装的所有属性和方法。
```python
class Animal:
def __int__(self):
pass
"""动物类"""
def sleep(self):
print('正在睡觉')
class Dog(Animal):
"""Dog类继承自Animal类"""
def __init__(self):
pass
class Cat(Animal): # 定义类时,在括号后面传入父类的类名,表示子类继承父类
"""Cat类继承自Animal类"""
def __int__(self):
pass
# Dog 和 Cat 都继承自Animal类,可以直接使用Animal类里的sleep方法
dog = Dog()
dog.sleep()
cat = Cat()
cat.sleep()
单继承
子类只继承一个父类,子类用于父类的所有的方法和属性。
class 类名(父类名):
pass
- 子类继承自父类,可以享受父类中已经封装好的方法,不需要再次定义
- 子类中应该根据职责,封装子类特有的属性和方法。
多继承
子类可以拥有多个父类,并且具有所有父类的属性和方法。
如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪个父类的方法?class 子类名(父类名1,父类名2...) pass
当然,开发中,应该尽量避免这种容易产生混淆的情况。如果多个父类之间存在同名的属性后者方法,应该尽量避免使用多继承。
MRO
- Python中针对类提供了一个内置属性
__mro__可以用来查看方法的搜索顺序。 MRO 是
method resolution order的简称,主要用于在多继承时判断方法属性的调用顺序。print(C.__mro__)输出结果:
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)在调用方法时,按照
__mro__的输出结果从左至右的顺序查找。- 如果再当前类中找到方法,就直接执行,不再向下搜索。
- 如果没有找到,就顺序查找下一个类中是否有对应的方法,如果找到,就直接执行,不再继续向下搜索。
-
新式类和旧式(经典)类
object是Python中所有对象的基类,提供了一些内置的属性和方法,可以时候用dir函数查看。 新式类:以
object为基类的类,推荐使用- 经典类:不以object为基类的类,不推荐使用
- 在 Python3.x 以后定义类时,如果没有指定父类,这个类会默认继承自 object,所以,python3.x版本定义的类都是新式类。
- 在Python2.x中定义类时,如果没有指定父类,则不会继承自object.
为了保证代码在Python2.x和Python3.x中都能够运行,在定义类时,如果一个类没有父类,建议统一继承自’object’
class 类名(object):
pass
内置函数
Python中的身份运算符用来判断两个对象是否相等;isinstance用来判断对象和类之间的关系;issublcass用啊里判断类与类之间的关系。
身份运算符
身份运算符用来比较两个对象的内存地址,看这两个对象是否是同一个对象。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person('张三', 18)
p2 = Person('张三', 18)
p3 = p1
print(p1 is p2) # False
print(p1 is p3) # True
isinstance
instance内置函数,用来判断一个实例对象是否是由某一个类(或者它的子类)实例化创建出来的。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, name, age, score):
super(Student, self).__init__(name, age)
self.score = score
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
p = Person('tony', 18)
s = Student('jack', 20, 90)
d = Dog('旺财', '白色')
print(isinstance(p, Person)) # True.对象p是由Person类创建出来的
print(isinstance(s, Person)) # True.对象s是有Person类的子类创建出来的
print(isinstance(d, Person)) # False.对象d和Person类没有关系
issubclass
issubclass 用来判断两个类之间的继承关系。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, name, age, score):
super(Student, self).__init__(name, age)
self.score = score
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
print(issubclass(Student, Person)) # True
print(issubclass(Dog, Person)) # False
多态
面向对象的三大特性:
- 封装:这是定义类的准则,根据对象的特点,将行为和属性抽象出来,封装到一个类中。
- 继承:这是设计类的技巧。父类与子类,主要体现在代码的重用,不需要大量的编写重复代码。
多态:不同的子类调用相同的父类方法,产生不同的执行结果,可以增加代码的外部灵活度。多态是以继承和重写父类方法为前提的,它是一种调用方法的技巧,不会影响到类的内部设计。
场景
提供三个类:缉毒犬、军犬、人
- 缉毒犬—>追查毒品,军犬—>攻击假人,人—>让小狗干活
- 设计类来完成功能。

class ArmyDog(object):
def bite_enemy(self):
print('追击敌人')
class DrugDog(object):
def track_drug(self):
print('追查毒品')
class Person(object):
def work_with_army(self, dog):
dog.bite_enemy()
def work_with_drug(self, dog):
dog.track_drug()
ad = ArmyDog()
dd = DrugDog()
p = Person()
p.work_with_army(ad)
p.work_with_drug(dd)
思考:这段代码设是否有问题?
新增需求:此时,又多了一个犬种,就又需要在Person类里新建一个方法,让这个方法操作新的狗。
class XiaoTianDog(object):
def eat_moon(self):
print('哮天犬把月亮吃了')
class Person(object):
def work_with_xiaotian(self, dog): # 添加方法
dog.eat_moon()
Person 类总是不断的添加新的功能,每次都需要改动Person类的源码,程序的扩展性太差了!
- 最好是提供一个父类 Dog,具备 work 的功能,其他小狗继承它,这样只要是小狗类,则行为被统一起来了,我们人类完全可以保证,只要是小狗的子类,找它干活肯定不会有问题。
- 这样人只要一个方法就能逗任意种类的狗玩,哪怕是添加新的狗,人的类都不需要修改。
图示如下:

class Dog(object):
def work(self): # 父类提供统一的方法,哪怕是空方法
pass
class ArmyDog(Dog): # 继承 Dog
def work(self): # 子类重写方法,并且处理自己的行为
print('追击敌人')
class DrugDog(Dog):
def work(self):
print('追查毒品')
class Person(object):
def work_with_dog(self, dog):
dog.work() # 使用小狗可以根据对象的不同而产生不同的运行效果, 保障了代码的稳定性
# 子类对象可以当作父类来使用
dog = Dog()
ad = ArmyDog()
dd = DrugDog()
p = Person()
p.work_with_dog(dog)
p.work_with_dog(ad) # 同一个方法,只要是 Dog 的子类就可以传递,提供了代码的灵活性
p.work_with_dog(dd) # 并且传递不同对象,最终 work_with_dog 产生了不同的执行效果
最终效果
