最近在学极客时间上的专栏“设计模式之美”,决定跟随专栏的更新,立一个flag,坚持打卡每节都写一篇笔记,作为对学习过程的巩固,以加深理解和思考。本篇是专栏第5节的学习笔记,第5节是专栏中介绍"面向对象"这一设计原则和设计思想的一节。

在过去我们学习各种面向对象编程语言(比如java)时,一般多会说面向对象有三大特性: 封装、继承和多态。而专栏中也将"抽象"作为第四个特性加入其中。

封装(Encapsulation)

封装即信息隐藏或数据保护,“数据结构"通过暴露有限的访问接口,授权外部仅能通过"数据结构"提供的方法(函数)来访问其内部的数据。

几行代码胜千言,下面分别给出python和go中实现封装的示例代码:

 1import uuid
 2import time
 3import decimal
 4
 5class Wallet:
 6    def __init__(self):
 7        self.__id = uuid.uuid1().__str__()
 8        self.__create_time = int(time.time())
 9        self.__balance = decimal.Decimal('0.00')
10        self.__balance_last_modified_time = int(time.time())
11
12    @property
13    def id(self):
14        return self.__id
15
16    @property
17    def create_time(self):
18        return self.__create_time
19
20    @property
21    def blance(self):
22        return self.__balance
23
24    @property
25    def balance_last_modified_time(self):
26        return self.__balance_last_modified_time
27
28    def increase_balance(self, increased_amount):
29        if increased_amount < 0:
30            raise ValueError('...')
31        self.__balance += increased_amount
32        self.__balance_last_modified_time = int(time.time())
33
34    def decrease_balance(self, decreased_amount):
35        if decreased_amount < 0:
36            raise ValueError('...')
37        if decreased_amount > self.__balance:
38            raise ValueError('...')
39        self.__balance -= decreased_amount
40        self.__balance_last_modified_time = int(time.time())
 1import (
 2	"errors"
 3	"time"
 4
 5	"github.com/shopspring/decimal"
 6)
 7
 8type wallet struct {
 9	id                      string
10	createTime              time.Time
11	balance                 decimal.Decimal
12	balanceLastModifiedTime time.Time
13}
14
15func NewWallet() wallet {
16	return wallet{
17		id:                      "",
18		createTime:              time.Now(),
19		balance:                 decimal.NewFromFloat(0),
20		balanceLastModifiedTime: time.Now(),
21	}
22}
23
24func (w *wallet) Id() string {
25	return w.id
26}
27
28func (w *wallet) CreateTime() time.Time {
29	return w.createTime
30}
31
32func (w *wallet) Blance() decimal.Decimal {
33	return w.balance
34}
35
36func (w *wallet) BalanceLastModifiedTime() time.Time {
37	return w.balanceLastModifiedTime
38}
39
40func (w *wallet) IncreaseBalance(increasedAmount decimal.Decimal) error {
41	if increasedAmount.LessThan(decimal.NewFromFloat(0)) {
42		return errors.New("...")
43	}
44	w.balance = w.balance.Add(increasedAmount)
45	w.balanceLastModifiedTime = time.Now()
46	return nil
47}
48
49func (w *wallet) DecreasedBalance(decreasedAmount decimal.Decimal) error {
50	if decreasedAmount.LessThan(decimal.NewFromFloat(0)) {
51		return errors.New("...")
52	}
53	if decreasedAmount.GreaterThan(w.balance) {
54		return errors.New("...")
55	}
56	w.balance = w.balance.Sub(decreasedAmount)
57	w.balanceLastModifiedTime = time.Now()
58	return nil
59}

封装这个特性需要使用编程语言提供的"访问控制权限"这个语法机制来实现。封装主要是讲如何隐藏信息、保护数据。

抽象(Abstraction)

抽象讲的是如何隐藏方法实现,让方法调用者只关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。 在面向对象编程中经常借助接口(或抽象类)的语法机制来实现抽象。

几行代码胜千言,下面分别给出python和go中实现抽象的示例代码:

 1
 2from abc import ABC, abstractmethod
 3
 4class PictureStorage(ABC):
 5    
 6    @abstractmethod
 7    def save_picture(self, picure):
 8        pass
 9
10    @abstractmethod
11    def get_picture(self, picture_id):
12        pass
13
14class PictureStorageImpl(PictureStorage):
15    def save_picture(self, picture):
16        print('...')
17
18    def get_picture(self, picture_id):
19        print('...')
 1type Picture struct {
 2	//...
 3}
 4
 5type PictureStorage interface {
 6	SavePicture(picture *Picture) error
 7	GetPicture(pictureId string) (*Picture, error)
 8}
 9
10type pictureStorageImpl struct {
11}
12
13func (p *pictureStorageImpl) SavePicture(picture *Picture) error {
14	// ...
15	return nil
16}
17
18func (p *pictureStorageImpl) GetPicture(pictureId string) (*Picture, error) {
19	// ...
20	return nil, nil
21}

实际中抽象这个特性是很容易实现的,并不一定需要依赖接口和抽象类这类语法机制,例如在非面向对象语言中编写的函数(方法)本身就具有抽象的特性,即函数(方法)的使用者并不需要详细去了解函数(方法)内部具体的实现逻辑,而只要通过函数(方法)的命名、注释或文档即可了解其提供的功能后就可以直接使用了。这也是为什么我们经常说面向对象有"封装”、“继承”、“多态"三大特性,而没有包含"抽象"这第四个特性的原因。

延展一下,在广义上,抽象是一种非常宽泛的设计思想,很多的设计原则都体现了抽象这种思想,如基于接口而非实现编程、开闭原则(对扩展开放、对修改关闭)、代码解耦等等。

继承(Inheritance)

继承用来表示is-a的关系。从继承关系来说有单继承多重继承两种模式。 为了实现继承特性,编程语言需要提供特殊的语法机制来支持。例如: java使用extends关键字,python中使用()来实现继承。 有的语言只支持单继承,如java;有的语言支持多重继承,如python。

继承的好处是可以实现代码复用,但继承这个特性有很大争议,不应过度使用。例如如果继承的层次过深就会导致代码可读性和可维护性变差。 因此我们平时要少用继承而多用组合。这也是go语言不支持继承特性而提倡使用组合的原因

多态(polymorphism)

多态是指子类可以替换父类,在实际代码运行过程中调用子类的方法实现。 多态这种特性也需要编程语言提供特殊的语法机制来实现。

多态可以通过"子类继承父类+子类重写父类方法+父类引用指向子类对象"的方式实现,另外还可以通过"接口语法"的方式实现,这两种方式在Java中都支持。 多态另外还可以通过第三种方式即duck-typing的语法实现,这种实现方式一般只有一些动态语言如Python才支持,另外go语言中的"隐藏式接口"也算是duck-typing

下面示例给出duck-typing实现多态在python和go中的示例代码:

 1
 2class Logger:
 3    def record(self):
 4        print('I write a log into file.')
 5        
 6class DB:
 7    def record(self):
 8        print('I insert data into db. ')
 9        
10def test(recorder):
11    recorder.record()
12
13def demo():
14    logger = Logger()
15    db = DB()
16    test(logger)
17    test(db)

从上面的python代码中可以看出,Logger和DB两个类没有任何关系,既不是继承关系,也不是接口和实现的关系,但在运行时它们的对象却都可以传递到test()方法中,并执行各自的record()方法。

 1package main
 2
 3type Recorder interface {
 4	Record() error
 5}
 6
 7type Logger struct {
 8}
 9
10func (l *Logger) Record() error {
11	return nil
12}
13
14type DB struct {
15}
16
17func (db *DB) Record() error {
18	return nil
19}
20
21func Test(recorder Recorder) {
22	recorder.Record()
23}
24
25func main() {
26	logger := &Logger{}
27	db := &DB{}
28	Test(logger)
29	Test(db)
30}

多态可以提高代码的可扩展性和可复用性。多态可以说是面向对象中最重要的一个特性,是很多设计模式、设计原则、编程技巧的代码实现基础,例如策略模式、基于接口而非实现编程、依赖倒置原则、里氏替换原则等等。

参考