设计模式之美学习笔记05: 封装、抽象、继承、多态,面向对象的四大特性
2019-11-13
最近在学极客时间上的专栏“设计模式之美”,决定跟随专栏的更新,立一个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}
多态可以提高代码的可扩展性和可复用性。多态可以说是面向对象中最重要的一个特性,是很多设计模式、设计原则、编程技巧的代码实现基础,例如策略模式、基于接口而非实现编程、依赖倒置原则、里氏替换原则等等。