[编程算法] 到底什么是“面向对象编程(OOP)”
序言
本篇介绍了对“程序员”来说很可能是第一个难点ever,但又绝不能不知道的面向对象编程(Object Oriented Programming)
和面向过程编程(Procedural Oriented Programming)
,由于我自己本科学的是金融数学,二硕学个计算机吧又是金融计算方向(选课偏向数据分析
而非软件开发
),因此基本上在编程的时候使用的都只是简单易懂的“面向过程编程”
的方法,因此本篇我就要好好研究一下到底什么叫“面向对象编程”
。
正文
背景介绍
在写上一篇博文的时候,我把Selenium with Python的文档看了一遍,看着看着忽然觉得有些吃力,这才想起来我有一个东西一直都是半懂不懂,那就是传说中所谓的面向对象编程(Object Oriented Programming)
和面向过程编程(Procedural Oriented Programming)
。这导致我在看别人代码的时候总是没有那种融汇贯通心领神会的感觉,作为“1/4”
个计算机“科班出生”
的“1/4程序员”
这怎么行?所以我今天就要把这个东西彻底啃透。
概述
根据这篇知乎文章的解释,面向过程
与面向对象
这两种编程方式拥有各自的优缺点。通俗一点解释就是**:**
-
面向过程: 顾名思义,编程方式注重过程。在解决一个问题的时候这种编程方式需要把解决方式拆分成一个个过程,按照一定的顺序执行完这些方法(methods),方法执行完问题就解决了。
-
**面向对象:**同样名字就已经非常清晰了,注重的是程序互动的对象。解决问题的时候面向对象编程是将事物抽象成对象,那问题里可能涉及的事物都定义成一个程序对象,然后给这个对象赋上一些
属性(Properties)
和方法(methods)
。让各个对象去执行自己的方法以此来解决问题。
举例说明
我引用呜呜轩轩提到的我觉得对于我的理解起到重要帮助的两个例子,分别是洗衣服和做饭。
洗衣服
假设有一个问题:要用洗衣机洗干净脏衣服,如何执行?
面向过程
的解决方法:
- 执行加洗衣粉的方法;
- 执行加水的方法;
- 执行洗衣服的方法;
- 执行清洗的方法;
- 执行烘干的方法;
将解决过程拆分成一个个方法,不用调用对象,通过执行一个个方法来解决问题。
面向对象
的解决方法:
- 先抽象出两个对象,分别是
洗衣机
和人
- 针对对象
洗衣机
加入一些属性和方法:“洗衣服方法”,”清洗方法“,”烘干方法“ - 针对对象
人
加入一些属性和方法:“加洗衣粉方法”,“加水方法” - 然后执行:
人.加洗衣粉→人.加水→洗衣机.洗衣服→洗衣机.清洗→洗衣机.烘干
同一个问题,面向对象编程需要先定义对象然后靠对象执行方法的方式解决问题。
做饭
呜呜轩轩引用了@十四期_李光的说法,分别将面向过程编程
和面向对象编程
比喻成蛋炒饭
和盖浇饭
。以此来说明两种方法的优缺点:
面向过程编程就像蛋炒饭
,食材和饭交融在一起,难以分开,其味道可能均匀易于入口,但一旦做好就很难再改变。面向对象编程就像盖浇饭
,食材和饭是分开的,食客在同时能享受到食材和饭的同时,如果想要换一个口味也只需要将不同的菜与之前的饭放在一起就可以了。
回到现实,这两种方法各自的优缺点就是:
- 面向过程编程性能高于面向对象编程,缺点是不易维护,难以扩展,难以复用。适合需要性能优先的场景。
- 面向对象编程就是相反了,性能略低但易维护易扩展,结构更加灵活。适合大而复杂的软件开发。
这两个例子一个让人了解这两种方法在表达形式上的区别,一个让人了解各自的优缺点,我觉得非常方便概念比较模糊的人理解。
面向对象编程
我个人觉得面向过程编程
是非常straightforward
的,所以除了两者的对比以外,我不打算细说面向过程编程。那在这一节我就还是用我从网上学到的内容(主要还是呜呜轩轩和Alexey)现学现卖,详细讲讲面向对象编程的三大特征和五大原则,以及其实际的实现方法等。
三大基本特征
**封装(Encapsulation):**把客观事物封装成抽象的类(Class)
,而类可以把自己的属性(Property)
和方法(methods)
只让可信的类或者对象操作,对不可信的进行信息隐藏(public和private)。一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。
**继承(Inheritance):**让某个类型的对象获得另一个类型对象的属性的方法。继承使新类可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展,通过继承创建的新类称为“子类(Subclass)”
或“派生类(Derived class)”
,被继承的类称为“基类(Base class)”
,“父类(Parent class)”
或“超类(Super class)”
。继承的过程就是从一般(generalized)
到特殊(specialized)
的过程。要实现继承,可以通过“继承(Inheritance)”
和“组合(Composition)”
来实现。继承概念的实现方式有两类:实现继承(Implementation inheritance)
与接口继承(Interface inheritance)
。前者指直接使用父类的属性和方法而无需额外编码的能力;后者则是指仅使用属性和方法的名称,但子类必须提供实现的能力。
**多态(Polymorphism):**指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着虽然针对不同对象的具体操作不同,但通过一个公共的类,它们可以通过相同的方式予以调用。
五大基本原则
**单一职责原则SRP(Single Responsibility Principle):**指一个类的功能要单一,不能混杂什么都有。
**开放封闭原则OCP(Open-Close Principle):**一个模块在扩展性方面应该是开放的,而在更改性方面应该是封闭的。比如:一个网络模块,原来只有服务端功能,在现在要加入客户端功能,那么就应该在不用修改已有功能代码的前提下就能增加客户端的实现代码,这要求在设计之初就将两个部分分开,将公共部分抽象出来。
**里式替换原则LSP(the Liskov Substitution Principle LSP):**子类应当可以替换父类并出现在父类能出现的任何地方。
**依赖倒置原则DIP(the Dependency Inversion Principle DIP):**程序要依赖于抽象接口而不能依赖于具体实现。假设B是较A低的模块,但B需要使用到A的功能,这个时候,B不应当直接使用A中的具体类,因为如果这样做的话任何一个下层模块的变动都会导致上层必须要被相应更改。正确做法应当由B定义一个抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口:这样就达到了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。
**接口分离原则ISP(the Interface Segregation Principle ISP):**模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来。
Python中的面向对象编程
Python中的类(Class)
一个类(Class)
会定义所有以它创建的对象,你甚至可以把类想象成一个对象工厂。举例来说,假设你有一只牛头梗
,从面向对象编程
的角度来看,一个狗
的类中可能包含狗的一些特征,比如他的品种,眼色等。由于牛头梗是一只狗,那么我们就可以创建一个继承狗
这个类
特征的对象来定义牛头梗。类
使用方法函数(methods)
和构造函数(constructors)
来创建并定义对象(Objects)
。
用户定义的方法(user-defined methods)和特殊方法(special methods)
方法(methods)
是类中用以执行特殊任务的函数,Python将方法分成程序员编写的用户定义方法(user-defined method)
和语言中自带的特殊方法(special method)
。比如一个叫狗
的类
中可能有一个方法叫做walk()
来供狗
的对象调用,程序员编写这个方法来执行特殊的动作。
而特殊方法
则被双下划线(__)
包围的名字定义,比如说__init__
。Python用这些特殊方法来加强类的功能性,其大多在后台工作并会在程序需要的时候被自动调用。程序员不能直接调用这些方法。举例来说,当你创建一个新的对象的时候,Python会自动调用__new__
方法,而这又会调用__init__
方法。
构造函数(constructor)
构造函数是程序在创建对象时会调用的特殊方法,其在类中是被用来初始化对象数据的。以狗
这个类举例,我们可以用这个类的构造函数去给每一个牛头梗
对象特征赋值,__init__
就是Python的构造函数。
用代码来解释
我们首先创建一个类,并且在这个类中定义一个__init__
方法,而这个方法则初始化两个属性(attribute)
:
1 | class Dog: |
这时我们已经有了一个类
了,接下来我们以这个类创建对象
,并且将类
中的属性
传递给这个对象
。这时候我们就需要self 这个magic word来将对象的属性绑定在其接收到的参数上。
1 | ...Tomita = Dog("Fox Terrier","brown")... |
在上面这行代码中,我们创建了一个叫做Tomita
的对象(这是这只狗的名字),然后我们首先声明定义这个对象的类的名字(Dog
),然后将在Dog类
的__init__
中设定的两个属性分别设定为Fox Terrier
和brown
。然后__init__
用self
来将这两个以参数传回的值赋给对象的属性self.breed
和self.eteColor
。
然后要访问我们创建的对象的属性,我们只需要在对象名后用.
接上相应的属性名,就可以访问这个对象的这个属性的值,例如:
1 | ...print("This dog is a",tomita.breed,"and its eyes are",tomita.eyeColor) |
我们运行上面这段代码会得到这样的结果:
This dog is a Fox Terrier and its eyes are brown
Python中的构造函数也可以不输入参数,可以使用默认参数设定。如果一个构造函数不是必须输入参数的话,这个构造函数就叫默认构造函数(default constructor)
。比如上面的Dog类
如果写成默认构造函数形式:
1 | class Dog: |
可以注意到__init__
中规定了两个参数的默认值dogBreed
为German Shepherd
而dogEyeColor
为Brown
,这时如果我们以tomita = Dog()
这样不传递任何参数的方式创建对象,那么这个对象的属性就会被设定成默认的German Shepherd
和Brown
。
最后
通过这篇文章,我们首先理清楚了面向过程编程
和面向对象编程
二者的关系以及各自的利弊,前半部分实际适用于所有的编程语言,之后我们重点关注Python中面向对象编程的方式,并举例来说明一些比较难以理解的参数和设定(比如__init__
和slef
等),搞懂了这些基础知识终于可以看懂一些大佬的代码到底在干什么了。
[第5篇]