面向对象

Posted lennie-luo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象相关的知识,希望对你有一定的参考价值。

面向对象 Object Oriented

一、概述

  面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。面向对象编程(OOP)具体说是一种程序开发方法,一种编程思想、范式。你可以粗滤的理解为,项目经理干的活就是面向对象,负责分配任务,而coder干的活就是面向过程,负责具体任务的实施细节。

1.面向过程:分析出解决问题的具体细节、步骤,然后逐步实现。

  1. 公式:程序 = 算法 + 数据结构
  2. 优点:所有环节、细节自己掌控。
  3. 缺点:考虑所有细节,工作量大。
  4. 找出解决问题的人,然后分配职责。

2.面向对象:找出解决问题的人,然后分配职责,不关注实施细节。

1.公式:程序 = 对象 + 交互

2.优点

(1) 思想层面:

-- 可模拟现实情景,更接近于人类思维。

-- 有利于梳理归纳、分析解决问题。

(2) 技术层面:

-- 高复用:对重复的代码进行封装,提高开发效率。

-- 高扩展:增加新的功能,不修改以前的代码。

-- 高维护:代码可读性好,逻辑清晰,结构规整。

3.缺点:学习曲线陡峭。

二、类和对象

1.类(class):具有相同属性(实例变量)和行为(实例方法)的对象的抽象。实际上就是一种自定义的数据类型,类也有对应的类变量和类方法。

2.对象(instance):类的具体实例,即归属于某个类别的”个体”。

3.属性和行为:类与类方法不同,对象与对象属性值不同

1.类和对象的创建

(1)创建类

1.格式

class 类名(object):

“””文档说明”””

def _init_(self,参数列表):

self.实例变量名= 参数(形参)

def  实例方法名(self,参数列表):

pass

2.说明

--   类名所有单词首字母大写,多个单词不用下划线隔开采用驼峰体。

-- (object)是表示该类是从object类继承下来的,python3.x版本可省略

-- __init__也叫构造函数,创建对象时被调用,也可以省略。

-- self绑定的是被创建的对象的内存地址,名称可以随意。

拓展:object类和type类的区别,即基类与元类的区别

1.object类是继承层面的:其他的类或者对象都是通过继承的关系,直接或者间接的继承了object,翻阅所有对象的族谱,最后一定会发现它们的老祖宗就是object。

1 >>>list.__base__    #Python为所有类都提供了一个__bases__属性,通过该属性可以查看该类的所有直接父类,该属性返回所有直接父类组成的元组
  (<class object>,)
2 >>>type.__bases__
  (<class object>,)
3 >>>object.__bases__   #object类是所有类的基类
  ()

2.type类是类创建对象层面的:python中一切皆对象,数据、对象是由str、list等数据类型和自定义类创建的,而所有数据类型和自定义类都是由元类创建的

1 >>> type(list)
  <class type>
2 >>> type(object)
  <class type>
3 >>> type(type)
  <class type>    #type类是所有类的元类,但是其本身是由虚拟机创建的

(2)创建对象(实例化)

1.格式

变量 = 类名 (实参数列表)

2.说明

--  python中无处不对象,对象是python中对数据的一种抽象的表示

 

--  所有对象都有三种特性:id、类型、值

id:对象的内存地址,由变量关联,查看通过:id(变量)

类型:生成对象的模型,对应类名,查看通过:type(变量)

值:对象中存放的数据,对应实参列表,查看通过:变量.__dict__

 

--  每个对象都是由其对应的类创建出来的

拓展:与对象的创建,调用,删除相关的魔法方法:__new__()方法,__init__()方法,__call__()方法,__del__()方法

1.__new__()方法和__init__()方法:__new__()方法是一个类方法,在对象被创建的时候调用,该方法负责创建(实例化)一个对象。如果创建对象成功,该对象会自动的调用__init__()方法,对自身进行初始化,如果创建对象失败,没有对象了,自然也就没谁去调用__init__()方法了。

2.__call__()方法:如果类中定义了该方法,可以使对象成为像函数和类一样的可调用对象。

 1 class Entity:
 2     ‘‘‘
 3     调用实体来改变实体的位置。
 4     ‘‘‘
 5 
 6     def __init__(self, size, x, y):
 7         self.x, self.y = x, y
 8         self.size = size
 9 
10     def __call__(self, x, y):
11         ‘‘‘改变实体的位置‘‘‘
12         self.x, self.y = x, y
13 
14 
15 e = Entity(1, 2, 3)  # 创建实例
16 print(e.__dict__)  # ‘x‘: 2, ‘y‘: 3, ‘size‘: 1
17 e(4, 5)  # 实例可以象函数那样执行,并传入x y值,修改对象的x y
18 print(e.__dict__)  # ‘x‘: 4, ‘y‘: 5, ‘size‘: 1

3.__del__()方法:是python垃圾回收机制的实际应用,当类中定义了该方法时,创建的对象的引用计数为0时该方法被调用

>>> class D(object):
    def __init__(self):
        print this is D.__init__()
    def __del__(self):
        print this is D.__del__()

>>> d = D()
this is D.__init__()
>>> d2 = d
>>> d3 = d
>>> 
>>> del d
>>> del d2
>>> del d3   #当对象的引用计数为0时自动触发__del__()方法
this is D.__del__()

 

2.实例成员

(1)实例变量

1.语法

(1) 定义:对象地址.变量名=数据值

(2) 调用:对象地址.变量名=数据值

2.说明

(1) 首次通过对象赋值为创建,再次赋值为修改。

(2) 通常在__init__构造函数中(self.实例变量名=形参)创建,创建对象时赋值。也可以在

(3) 每个对象存储一份,通过对象地址访问

 3.作用:描述对象自身的数据。

 4.__dict__对象的属性,用于存储自身实例变量(包括私有化变量)的字典。

(2)实例方法

1.语法

(1) 定义: def  方法名称(self, 形参列表):

方法体

(2) 调用: 对象地址.实例方法名(实参列表)

2.说明

(1) 定义时至少有一个形参,一般命名为"self",用于绑定调用这个方法的对象地址,通过对象调用实例方法时self不用传参

(2)每个对象共用一份,通过对象地址访问。不建议通过类名访问实例方法(对象与对象数据值不同,通过类名调用实例方法时,需要给self参数传参具体的对象)

3.作用:表示对象行为。

 

 1 """
 2     实例成员
 3         记住一句话:实例成员,使用对象地址访问.
 4 """
 5 class Student:
 6     pass
 7 
 8 s01 = Student()
 9 # 定义实例变量: 对象.变量名 = 数据值
10 s01.name = "lennie"
11 print(s01.name)  #"lennie"
12 s02 = Student()
13 # print(s02.name)# AttributeError,因为s02指向的对象,没有创建过实例变量name
14 # 通过__dict__获取当前对象的所有实例变量
15 print(s01.__dict__)  #‘name‘: ‘lennie‘
16 print(s02.__dict__)  #
17 
18 
19 class Student02:
20     def __init__(self, name, height):
21         self.name = name
22         self.height = height
23 
24     # 实例方法
25     def print_self(self):
26         print(self.name,self.height)
27 
28 
29 s01 = Student02("lennie", 170)
30 s02 = Student02("ginger", 162)
31 # 建议:实例方法,通过对象地址访问.
32 s01.print_self()
33 # 不建议:Student02.print_self(),若没有传递对象地址,实例方法不能正确访问对象数据.
34 Student02.print_self(s02)

 

3.类成员

(1)类变量

1.语法

(1) 定义:在类中,方法外定义变量。

class 类名:

   变量名 = 表达式

(2) 调用:类名.变量名

      不建议通过对象访问类变量

2.说明

(1) 存储在类中。

(2)所有对象共享一份,可通过类或者对象直接调用

3.作用:描述所有对象的共有数据。

(2)类方法

1.语法

(1) 定义:

    @classmethod

    def 方法名称(cls,形参列表):

         方法体

(2) 调用:类名.方法名(实参列表)

      不建议通过对象访问类方法

2.说明

(1) 至少有一个形参,一般命名为‘cls‘,用于绑定类的内存地址。

(2) 使用@classmethod修饰的目的是调用类方法时可以隐式传递类,即通过类名调用类方法时cls不需要传参。

(3) 类方法中不能访问实例成员,实例方法中可以访问类成员

3.作用:操作类变量。

 

 1 """
 2     类成员,用类的地址去访问
 3 """
 4 class ICBC:
 5     # 类变量:总行的钱
 6     total_money = 1000000
 7     # 类方法
 8     @classmethod
 9     def print_total_money(cls):
10         # print(id(cls), id(ICBC))
11         # cls : 存储当前类的地址
12         # print("当前总行金额:",ICBC.total_money)
13         print("当前总行金额:", cls.total_money)
14 
15     def __init__(self, name, money):
16         self.name = name
17         self.money = money
18         # 从总行扣除当前支行的钱
19         ICBC.total_money -= money
20 
21 
22 i01 = ICBC("天坛支行", 100000)
23 i02 = ICBC("陶然亭支行", 100000)
24 # 主流:通过类访问类成员
25 ICBC.print_total_money()  #"当前总行金额: 800000"
26 print(ICBC.total_money)   #800000
27 # 非主流:通过对象访问类成员
28 # print(i02.total_money)
29 # i02.print_total_money()

 

(3)静态方法

1.语法

(1) 定义:

    @staticmethod

    def 方法名称(形参列表):

            方法体

(2) 调用:类名.方法名(实参列表)

      不建议通过对象访问静态方法

2.说明

(1) 使用@ staticmethod修饰的目的是该方法不需要隐式传参数,即参数列表不含selfcls参数,其实就是个函数

(2) 静态方法不能访问实例成员和类成员

3.作用:定义常用的工具函数

三、三大特征

1.封装

(1)数据角度讲

1.定义:将一些基本属性封装成共有属性,类的创建中的__init__初始化

2.优势:将数据与对数据的操作相关联,代码可读性(相比容器)更高(类是对象的模板)。

(2)行为角度讲

  1. 定义:类外提供必要的功能,隐藏实现的细节,私有化操作

  2. 优势:简化编程,使用者不必了解具体的实现细节,只需要调用对外提供的功能。

  3. 私有成员:

(1) 作用:只有类内部可以直接访问,外部不能直接访问,可以对参数做检查,避免传入无效的参数

(2) 做法:__成员名

(3) 本质:障眼法,实际也可以访问。私有成员的名称被修改为:_类名__成员名,可以通过_dict_属性或dir函数查看。

 

 1 class Coder:
 2     """
 3     私有成员的读和写操作
 4     """
 5     def __init__(self, name, age):
 6         self.name = name
 7         self.__age = age  #私有化方式一:私有成员命名采用双下划线开头
 8         # self.set_age(age)  #私有化方式二
 9 
10     # 使用两个公开的方法获取和修改私有属性
11     def get_age(self):
12         return self.__age  # 类内部访问无限制
13 
14     def set_age(self, value):
15         if 0 <= value <= 100:  # 检验数据的有效性,限制随意更改属性值
16             self.__age = value
17         else:
18             raise ValueError
19 
20     age=property(get_age,set_age)  #无读写限制
21     # age=property(get_age,None)  #写限制
22     # age=property(None,set_age)  #读限制
23     # age=property(None,None)  #读写均限制
24 
25 c = Coder("lennie", 26)
26 # 1.私有成员不能直接访问
27 print(c.__age)  #AttributeError: ‘Coder‘ object has no attribute ‘__age‘
28 print(c.__dict__)  # ‘name‘: ‘lennie‘, ‘_Coder__age‘: 26
29 # 2.可通过 对象._类名__成员名 访问(此种访问无限制)
30 c._Coder__age = 27
31 print(c._Coder__age)  # 27
32 # 3.可通过两个私有方法实现私有成员的读写(此种方式可限制私有成员被随意改写)
33 c.set_age(30)
34 print(c.get_age())  # 30
35 #4.可通过设置property(读方法名,写方法名),实现私有成员的读写
36 c.age=32
37 print(c.age)  #32

 

 

4.属性@property

公开的实例变量,缺少逻辑验证。私有的实例变量与两个公开的方法相结合,又使调用者的操作略显复杂。而属性可以将两个方法的使用方式像操作变量一样方便。

(1) 定义:

@property             #允许读,等价于定义类变量:属性名=property(读方法名,None)

 

def 属性名(self):

return self.__属性名

@属性名.setter              #允许写,等价于定义类变量:属性名=property(读方法名,None)

def 属性名(self, value):

self.__属性名= value

(2) 调用:

对象.属性名 = 数据

变量 = 对象.属性名

(3) 说明:

  • 通常两个公开的属性,保护一个私有的变量。
  • @property 负责读取,@属性名.setter 负责写入
  •  只写:属性名= property(None, 写入方法名)

 

 1 class Coder:
 2     """
 3     @property和@私有成员名.setter
 4     """
 5     def __init__(self, name, age):
 6         self.name = name
 7         self.__age = age  #私有成员
 8 
 9     @property
10     def age(self):
11         return self.__age
12 
13     @age.setter
14     def age(self, value):
15         if 0 <= value <= 100:  # 检验数据的有效性,限制随意更改属性值
16             self.__age = value
17         else:
18             raise ValueError
19 
20 c=Coder("lennie",26)
21 # print(c.__age)  #依然不能直接调用,AttributeError
22 #1.可直接由_类名__属性名调用
23 c._Coder__age=27
24 print(c._Coder__age)  #27
25 #2.避免使用两个方法的繁琐,使私有成员如变量般调用
26 c.age=30
27 # c.age=120   #ValueError
28 print(c.age)  #30

 

(3)设计角度讲

  1. 定义:

(1) 分而治之

将一个大的需求分解为许多类,每个类处理一个独立的功能。 设计角度看类的创建应先考虑方法(行为,干什么),在考虑初始化(数据)

(2) 变则疏之  #拆分的度

变化的地方独立封装,避免影响其他类。

(3) 高 内 聚

类中各个方法都在完成一项任务(单一职责的类)

(4) 低 耦 合    #低不是杜绝,是有互相调用才能完成

类与类的关联性与依赖度要低(每个类独立),让一个类的改变,尽少影响其他类。

2.优势:

便于分工,便于复用,可扩展性强。

2.继承

(1)语法角度讲

  • 继承方法
  1. 语法:

class 父类:

def 父类方法(self):

    方法体

class 子类(父类)

def 子类方法(self):

方法体

儿子 = 子类()

儿子.子类方法()

儿子.父类方法()

2.说明:子类直接拥有父类的方法.

  • 内置函数
  1. isinstance(对象, 类型返回bool值,判断对象是否是类的对象。
  2. issubclass(类型1,类型2)返回bool值,判断类型1是否是类型2子类。
  • 继承数据
  1. 语法

class 子类(父类):

def __init__(self,参数列表):

super().__init__(参数列表)

self.自身实例变量 = 参数

2.说明

子类如果没有构造函数,将自动执行父类的,但如果有构造函数将覆盖父类的,此时必须通过super()函数调用父类的构造函数,以确保父类实例变量被正常创建。

  • 定义

重用现有类的功能,并在此基础上进行扩展。子类直接具有父类的成员(共性),还可以扩展新功能

 

  • 优点

一种代码复用的方式。

  • 缺点

耦合度高:父类的变化,直接影响子类

(2)设计角度讲

  • 定义

将相关类的共性进行抽象,统一概念,隔离变化。

  • 适用性

多个类在概念上是一致的,且需要进行统一的处理。

  • 相关概念

父类(基类、超类)、子类(派生类)。

父类相对于子类更抽象,范围更宽泛;子类相对于父类更具体,范围更狭小。

单继承:父类只有一个(例如 JavaC#)。

多继承:父类有多个(例如C++Python)。

Object类:任何类都直接或间接继承自 object 类。

  • 多继承

一个子类继承两个或两个以上的基类,父类中的属性和方法同时被子类继承下来。

同名方法的解析顺序(MROMethod Resolution Order:print(D.mro)

类自身 --> 父类继承列表(由左至右)--> 再上层父类

      A

     /   \

    B     C

     \   /

      D

3.多态

(1)设计角度讲

  • 定义

父类的同一种动作或者行为,在不同的子类上有不同的实现

  • 作用
  1. 在继承的基础上,体现类型的个性化(一个行为有不同的实现)。
  2. 增强程序扩展性,体现开闭原则。

(2)语法角度讲

  • 重写

子类实现了父类中相同的方法(方法名、参数)。

在调用该方法时,实际执行的是子类的方法。

  • 快捷键

Ctrl + O

  • 内置可重写函数

Python中,以双下划线开头、双下划线结尾的是系统定义的成员。我们可以在自定义类中进行重写,从而改变其行为。

  • 转换字符串

__str__函数:将对象(打印时显示内存地址)转换为字符串(打印对象时显示自定义字符串)(对人友好的)

__repr__函数:将对象转换为字符串(解释器可识别的)

  • 运算符重载

定义:让自定义的类生成的对象(实例)能够使用运算符进行操作。

  • 算数运算符

 

  • 反向算数运算符重载

 

  • 复合运算符重载(不产生新对象)

 

  • 比较运算重载

 

四、六大设计原则

1.开-闭原则(目标、总的指导思想)

Open Closed Principle

对扩展开放,对修改关闭。

增加新功能,不改变原有代码。

2.类的单一职责(一个类的定义)

Single Responsibility Principle   

一个类有且只有一个改变它的原因。

3.依赖倒置(依赖抽象)

Dependency Inversion Principle

客户端代码(调用的类)尽量依赖(使用)抽象。

抽象不应该依赖细节,细节应该依赖抽象。

4.组合复用原则(复用的最佳实践)

Composite Reuse Principle

如果仅仅为了代码复用优先选择组合复用,而非继承复用。

组合的耦合性相对继承低。

5.里氏替换(继承后的重写,指导继承的设计)

Liskov Substitution Principle

父类出现的地方可以被子类替换,在替换后依然保持原功能。

子类要拥有父类的所有功能。

子类在重写父类方法时,尽量选择扩展重写,防止改变了功能。在Ctrl+o时,别删东西,重写后扩展重写

6.迪米特法则(类与类交互的原则)

Law of Demeter

不要和陌生人说话。

类与类交互时,在满足功能要求的基础上,传递的数据量越少越好。因为这样可能降低耦合度(利用抽象去调用子类,隔离变化)。

 

以上是关于面向对象的主要内容,如果未能解决你的问题,请参考以下文章

224 面向对象编程介绍,面向过程与面向对象

面向对象-面向对象和面向过程的区别

面向对象分析与设计面向对象设计包括哪些内容

面向对象

面向过程面向对象及面向对象的三大特征

Python面向对象学习 1 (什么是面向对象,面向对象的应用场景)