前面我们讲解了面向对象的基础,没有涉及到一行代码。这些看似很枯燥的概念,其实是非常重要的。如果不能及时理解或掌握,可以慢慢来。
面向对象更进一步的抽象了世界。OOP的世界观:
面向对象的特性:
面向对象的三大特征:
面向对象最重要的概念就是类(Class
)和实例(Instance
),必须牢记类是抽象的模板,而实例则是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据有可能不同。
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。
面向对象的本质:对行为和数据的封装;有时候数据就是数据;而有的时候行为就是行为。我们先使用Python标准库中的namedtuple
来实现一个入门的类吧。目的是为了组织数据。命名元组的优势:组织的更好,字段有名字。
from collections import namedtuple
Door = namedtuple('Door', ['number', 'status'])
# 实例化
door = Door(10010, 'closed')
print(door.status)
print(door.number)
: closed
: 10010
以面向对象的方式实现Door,
class Door:
def __init__(self, number, status):
# . 用于访问对象的属性与方法
self.number = number
self.status = status
door = Door(10010, 'closed') # 调用初始化方法(其他语言中的构造方法)
print(door.number) # 获取属性,输出:10010
print(door.status) # 获取属性,输出closed
类就是数据与逻辑(或动作)的集合。上述的Door类中只有数据没有逻辑,那么我们在该类中加入开门与关门的动作,用来操纵类中的数据。上述的例子改写如下:
class Door:
def __init__(self, number, status):
self.number = number
self.status = status
def open_door(self):
self.status = 'opened'
def close_door(self):
self.status = 'closed'
door = Door(10010, 'opened')
print("door's number is: {}".format(door.number))
print("door's status is: {}".format(door.status))
print("现在关门做点坏事")
door.close_door()
print("door's status is: {}".format(door.status))
print("坏事做完,开启门窗透透气吧")
door.open_door()
print("door's status is: {}".format(door.status))
执行上述代码:
$ python3 door.py
door's number is: 10010
door's status is: opened
现在关门做点坏事
door's status is: closed
坏事做完,开启门窗透透气吧
door's status is: opened
上述代码中,我们通过open_door()
与close_door()
函数来操作了Door
类的status
数据。
如果大家写过C++
或Java
代码,可以很轻松地用C++
或Java
进行实现。我们看看C++
是如何实现上述代码的:
// filename: door.cpp
#include <iostream>
using namespace std;
class Door
{
public:
int number;
string status;
Door(int number, string status)
{
this->number = number;
this->status = status;
}
void open_door(void);
void close_door(void);
};
void Door::open_door(void)
{
this->status = "opened";
}
void Door::close_door(void)
{
this->status = "closed";
}
int main(int argc, char *argv[])
{
Door door(10010, "opened");
cout << "door's number is: " << door.number << endl;
cout << "door's status is: " << door.status << endl;
cout << "现在关闭门窗做点坏事" << endl;
door.close_door();
cout << "door's status is: " << door.status << endl;
cout << "坏事做完,开启门窗透透气吧" << endl;
door.open_door();
cout << "door's status is: " << door.status << endl;
return 0;
}
编译并运行。结果如下:
$ g++ door.cpp -o door
$ ./door
door's number is: 10010
door's status is: opened
现在关闭门窗做点坏事
door's status is: closed
坏事做完,开启门窗透透气吧
door's status is: opened
我们知道,Java
是源自于C++
的。那么我们看看如何用Java
代码该怎么写呢?
// filename: Door.java
class DoorConstructor {
int number;
String status;
DoorConstructor(int number, String status) {
this.number = number;
this.status = status;
}
public void close_door() {
this.status = "closed";
}
public void open_door() {
this.status = "opened";
}
}
public class Door {
public static void main(String args[]) {
DoorConstructor door = new DoorConstructor(10010, "opened");
System.out.println("door's number is: " + door.number);
System.out.println("door's status is: " + door.status);
System.out.println("现在关门做点坏事");
door.close_door();
System.out.println("door's status is: " + door.status);
System.out.println("坏事做完,开启门窗透透气吧");
door.open_door();
System.out.println("door's status is: " + door.status);
}
}
编译并运行:
$ javac Door.java
$ java Door
door's number is: 10010
door's status is: opened
现在关门做点坏事
door's status is: closed
坏事做完,开启门窗透透气吧
door's status is: opened
我们看看Go语言是如何使用面向对象的。先看代码:
package main
import "fmt"
type Door struct {
number int
status string
}
func (d *Door) close_door() {
d.status = "closed"
}
func (d *Door) open_door() {
d.status = "opened"
}
func main() {
door := Door{10010, "opened"}
fmt.Println("door's number is:", door.number)
fmt.Println("door's status is:", door.status)
fmt.Println("现在关门做点坏事")
door.close_door()
fmt.Println("door's status is:", door.status)
fmt.Println("坏事做完,开启门窗透透气吧")
door.open_door()
fmt.Println("door's status is:", door.status)
}
编译并运行:
$ go build door.go
$ ./door
door's number is: 10010
door's status is: opened
现在关门做点坏事
door's status is: closed
坏事做完,开启门窗透透气吧
door's status is: opened
上面我们通过四种支持面向对象的编程语言(当然还有很多编程语言,小白并没有拿它们一一举例。),简单地演示了这些语言的基本套路。这里所举的例子都是入门级的,限于小白的水平也做不到深入。举这些例子的目的是想告诉大家:面向对象编程只是一种思想,掌握了编程思想,那么使用什么样的语言来完成你的当前的任务就看这门语言提供了哪些特性、自己对这门语言的理解及熟练程度。
接下来会通过一些具体的实例说明实例化的过程。
In[14]: class Heap:
...: def __init__(self): # 此函数通常叫做构造函数,在Python中更多叫做初始化函数,在对象创建完成后会立刻执行
...: self.data = []
...: def add(self, x): # 第一个参数是self,其他参数与一般函数定义一样
...: self.data.append(x)
...: def pop(self):
...: if self.data:
...: self.data.pop()
...: else:
...: print('heap is empty')
...:
In[15]: heap = Heap() # 实例化Heap类,实例为heap
In[16]: heap.data
Out[16]: []
In[17]: heap.add(3)
In[18]: heap.add(4)
In[19]: heap.add(5)
In[20]: heap.data
Out[20]: [3, 4, 5]
In[21]: heap.pop()
In[22]: heap.pop()
In[23]: heap.data
Out[23]: [3]
In[24]: heap.pop()
In[25]: heap.data
Out[25]: []
In[26]: heap.pop()
heap is empty
上面代码中的self
代表heap这个实例。当然,代码中的self
并不一定要写为self
,还可以是其他的非关键字。
再来一个例子:
$ cat person.py
class Person: # 创建一个名为Person的类
def __init__(self, name, job=None, pay=0): # 初始化函数接收三个参数,与一般的函数参数具有相同意义
self.name = name # 创建对象时填充这些字段
self.job = job # self就是将要创建的对象(或实例)
self.pay = pay
bob = Person('Bob Smith') # test the class
sue = Person('Sue Jones', job='dev', pay=10000) # 自动执行__init__方法
print(bob.name, bob.pay) # 获取对象的属性
print(sue.name, sue.pay) # 不同的对象其自身的数据不一定相同
尽管上面的Person类非常简单,不过它依然演示了一些重要的内容。我们注意到bob的name并不是sue的name,并且sue的pay不是bob的pay。bob和sue它们都是两个独立的信息记录。从技术的角度来看,bob与sue都是namespace objects
,就像其他所有的类实例一样,它们创建时都有各自独立的状态信息的拷贝。因为每个类的实例都有自己self
属性的集合,可以把类可以理解为一个蓝图、工厂或模具。
一个示例,
class Door:
def __init__(self, number, status):
self.number = number
self.status = status
def open(self):
self.status = 'opened'
def close(self):
self.status = 'closed'
door = Door(1, 'closed') # 看起来非常像一个函数调用。事实上,
# 确实发生了一些函数调用,它调用了__init__函数,
# 第一个参数由解释器自动传入,表示实例本身,
# 通常命名为self,也可以为其他非关键字
print(door.__class__)
print(Door.__class__)
print(type.__class__)
# 所有类,都是type或者type的子类的实例
: <class '__main__.Door'>
: <class 'type'>
: <class 'type'>
__init__
函数并不会创建对象,__init__
函数初始化对象。对象(或实例)创建过程为:
__init__
函数实例怎么来的?由类的__new__
方法实现。如果要改变默认创建默认的创建实例的行为,可以写__new__
方法,不过通常是不写的。
class Door:
# def __new__(cls): # 创建实例的,可以改变实例创建的行为,这就是元编程的体现
# pass
def __init__(self, number, status):
self.number = number
self.status = status
def open(self):
self.status = 'opened'
def close(self):
self.status = 'closed'
door = Door(1, 'closed') # 看起来非常像一个函数调用。事实上,
# 确实发生了一些函数调用,它调用了__init__函数,
# 第一个参数由解释器自动传入,表示实例本身,
# 通常命名为self
print(door.__class__)
print(Door.__class__)
print(type.__class__)
# 所有类,都是type或者type的子类的实例
: <class '__main__.Door'>
: <class 'type'>
: <class 'type'>
实例化的时候,传递的参数列表是__init__
方法除了第一个参数之外的所有参数,支持函数的所有参数变化。
当没有显式的定义__init__
方法的时候,会使用默认的__init__
方法,
def __init__(self):
pass
通过.操作符访问实例的属性或者调用实例的方法。当我们调用实例方法的时候,第一个参数即实例本身,由解释器自动传入。
今天就到此为止吧,要写的内容太多了。内容写的太长的话,很多小伙伴估计会看得不耐烦。
预告一下明天的内容: