只用兩個函數實現事務的設計模式!

2020-12-09 CSDN

作者 | 梁唐 責編 | 張文

頭圖 | CSDN 下載自東方 IC

來源 | TechFlow(ID:techflow2019)

大家好,今天給大家介紹一個新的設計模式,叫做 memento 模式。

memento 在英文當中是紀念品的意思,在這裡,指的是對象的深度拷貝。通過對對象深度拷貝的方法來實現事務的功能。

有了解過資料庫的小夥伴應該都知道,在資料庫中有些操作是綁定的,要麼一起執行成功,要麼一起不執行,絕對不允許某些操作執行了,某些操作沒有執行的情況發生。這一點就被稱為事務。

深度拷貝

我們先來簡單回顧一下 Python 當中的拷貝。

拷貝在很多語言當中都有對應的函數,在 Python 當中也不例外。Python 中的拷貝函數有兩個,一個是 copy,另外一個是 deepcopy。也就是常說的深拷貝和淺拷貝,這兩者的區別也非常簡單,簡而言之就是淺拷貝只會拷貝父類對象,不會拷貝父類對象當中的子對象。

我們來看一個例子,在下圖當中 b 是 a 的淺拷貝,我們可以看到當 a[2] 當中插入了 5 之後,b 當中同樣也多了一個 5 。因為它們下標 2 存儲的是同一個引用,所以當 a 當中插入的時候,b 當中也發生了同樣的改變。我們也可以看到,當我們改變了 a[0] 的時候,b 當中則沒有發生對應的改變。因為a[0] 是一個數字,數字是基礎類型直接存儲的值而不是引用。

與淺拷貝對應的就是深拷貝,我們可以看到,當 a[2] 當中插入元素的時候,深度拷貝出來的 b 並不會發生對應的變化。

memento

利用拷貝,我們可以實現 memento 函數。它的作用是給對象做備份。在Python 當中,對於一個對象 obj 來說,它所有的成員以及函數等信息全是儲存在obj.__dict__這個 dict 當中的。也就是說如果我們將一個對象的__dict__拷貝一份的話,其實就相當於我們把對象拷貝了一份。

通過使用拷貝,我們可以很容易實現 memento 函數,先來看代碼:

from copy import copy, deepcopydefmemento(obj, deep=False): state = deepcopy(obj.__dict__) if deep else copy(obj.__dict__)defrestore(): obj.__dict__.clear() obj.__dict__.update(state)return restore

memento 是一個高階函數,它返回的結果是執行函數,而不是具體的執行結果。如果對高階函數不太熟悉的同學,可以去回顧一下 Python 當中高階函數的相關內容。

這裡面的邏輯不難理解,傳入的參數是一個 obj 的對象和一個 bool 型的flag。flag 表示使用深拷貝或淺拷貝,obj 就是我們需要做對應快照或者是存檔的對象。我們希望在對象框架不變的基礎上恢復其中的內容,所以我們拷貝的範圍很明確,就是 obj.__dict__,這當中存儲了對象的所有關鍵信息。

我們看下 restore 這個函數,當中的內容其實很簡單,只有兩行。

第一行是清空 obj 目前__dict__當中的內容,第二步是用之前保存的 state 來還原。其實 restore 執行的是一個回滾 obj 的功能,我們捋一下整個過程。

我們運行 memento 函數會得到 restore 這個函數,當我們執行這個函數的時候,obj 當中的內容會回滾到上次執行 memento 時的狀態。

理解了 memento 當中的邏輯之後,距離我們實現事務就不遠了。關於事務我們有兩種實現方法,一種是通過對象,一種是通過裝飾器,我們一個一個來說吧。

Transaction對象

面向對象實現的方式比較簡單,它和我們平時使用事務的過程也比較近似。Transaction 對象當中應該提供兩個函數,一個是 commit 一個是rollback。也就是說當我們執行成功之後我們執行 commit,對執行的結果進行快照。如果執行失敗則 rollback,將對象的結果回滾到上一次 commit時的狀態。

我們理解了 memento 函數之後,會發現 commit 和 rollback 剛好對應執行 memento 函數以及執行 restore 函數。這樣我們不難寫出代碼:

classTransaction: deep = False states = []def__init__(self, deep, *targets):self.deep = deepself.targets = targetsself.commit()defcommit(self):self.states = [memento(target, self.deep) for target inself.targets]defrollback(self):for a_state inself.states: a_state()

由於我們需要事務的對象可能不止一個,所以這裡的 targets 設計成了數組的形式。

Transaction裝飾器

我們也可以把事務實現成裝飾器,這樣我們可以通過註解的方式來使用

這裡的代碼原理也是一樣的,只不過實現邏輯基於裝飾器而已。如果對裝飾器熟悉的同學,其實也不難理解。這裡的 args[0] 其實就是某一個類的實例,也就是我們需要保證事務的主體。

from functools import wrapsdeftransactional(func): @wraps(func)defwrapper(*args, **kwargs):# args[0] is obj state = memento(args[0])try: func(*args, **kwargs)except Exception as e: state()raise ereturn wrapper

這是常規裝飾器的寫法,當然我們也可以用類來實現裝飾器,其實原理差不多,只是有一些細節不太一樣。

classTransactional:def__init__(self, method): self.method = methoddef__get__(self, obj, cls):deftransaction(*args, **kwargs): state = memento(obj)try:return self.method(*args, **kwargs)except Exception as e: state()raise ereturn transaction

當我們將這個註解加在某一個類方法上,當我們執行 obj.xxx 的時候,就會執行 Transactional 這個類中的__get__方法,而不是獲得 Transactional 這個類。並且把 obj 以及 obj 對應的類型作為參數傳入,也就是這裡的 obj 和 cls 的含義。這個是用類來實現裝飾器的常規做法,我們貼一下常規的代碼,來比較學習一下。

classWrapper:def__init__(self, func): wraps(func)(self)def__call__(self, *args, **kwargs):returnself.__wrapped__(*args, **kwargs)def__get__(self, instance, cls):if instance is None:returnselfelse:return types.MethodType(self, instance)

這是一個用類來實現裝飾器的 case,我們可以看到在__get__這個函數當中返回的是 self,也就是返回了 Wrapper 這個類。類通常是不能直接執行的,為了讓它能夠執行,這裡給它實現了一個__call__函數。用類實現裝飾器也不常見,我們熟悉高階函數的方法就可以了。

實戰

最後我們來看一個實際應用的例子,我們實現了一個 NumObj 的類,兼容了上面兩種事務的使用,可以對比一下看看區別。

classNumObj:def__init__(self, value): self.value = valuedef__repr__(self):return'<%s, %r>' % (self.__class__.__name__, self.value)defincrement(self): self.value += 1 @transactionaldefdo_stuff(self): self.value += '111' self.increment()if __name__ == '__main__': num_obj = NumObj(-1) a_transaction = Transaction(True, num_obj)# 使用Transactiontry:for i in range(3): num_obj.increment() print(num_obj) a_transaction.commit() print('----committed')for i in range(3): num_obj.increment() print(num_obj) num_obj.value += 'x' print(num_obj)except Exception: a_transaction.rollback() print('----rollback') print(num_obj)# 使用Transactional print('-- now doing stuff') num_obj.increment()try: num_obj.do_stuff()except Exception: print('-> doing stuff failed')import sysimport traceback traceback.print_exc(file=sys.stdout) print(num_obj)

從代碼當中,我們不難發現對於 Transaction 也就是面向對象實現的,我們需要額外創建一個 Transaction 的實例來在 try catch 當中控制是否執行回滾。而使用註解的方式更加靈活,它執行失敗會自動執行回滾,不需要太多的額外操作。

一般來說我們更加喜歡使用註解的方式,因為這樣的方式更加簡潔乾淨,更加pythonic,能夠體現出 Python 的強大。第一種方法顯得有些中規中矩,不過好處是可讀性強一些代碼實現難度也低一些

大家如果在實際工作當中有需要用到,可以根據自己的實際情況去進行選擇,兩種都是不錯的方法。

今天的文章就到這裡,衷心祝願大家每天都有所收穫。

相關焦點

  • Spring事務實現原理
    同時為了簡化事務管理器的實現,Spring提供了一個抽象類AbstractPlatformTransactionManager,規定了事務管理器的基本框架,僅將依賴於具體平臺的特性作為抽象方法留給子類實現。事務狀態——TransactionStatus事務狀態是我對TransactionStatus這個類的直譯。
  • 從函數式的角度重看GOF設計模式(一)
    在開始分析各種設計模式之前,先討論一個問題:簡單的英語語法練習。看這樣的句子:「smoking is unhealthy」和「running is tiring」。注意句子中的「smoking」和「running」,在英語中,ing後綴可以把一個名詞轉換動詞。GOF設計模式尤其是「行為模式」,也是採用相似的做法。
  • 是時候了解一下Spring中常用的設計模式了
    1.介紹設計模式是軟體開發的重要組成部分。這些解決方案不僅解決了反覆出現的問題,而且還通過識別通用模式來幫助開發人員了解框架的設計。在本教程中,我們將研究Spring框架中使用的四種最常見的設計模式:單例模式工廠方法模式代理模式
  • C語言——用函數實現模塊化程序設計
    之前,我們學習的程序設計,一個只能對應一個程序的運行,今天將的知識將教大家學會如何利用函數實現多程序的運用。通過前幾章的學習,我們已經能夠編寫一些簡單的C程序,但想要功能多規模大,將所有的程序代碼都寫在一個主函中,就會使主函數變得複雜,難以理解,頭緒不清,使閱讀和維護程序變得困難。此外有時候程序中要多次實現某一功能(例如列印每一頁的頁頭),就需要多次重複編寫實現此功能的程序代碼,這使程序冗長、不精練。因此,人們自然會想到採用「組裝」的辦法來簡化程序設計的過程。
  • 設計模式 | Iterator設計模式
    1.0 前言 此文是日本作者結城浩著作的《圖解設計模式》的譯作,原文用java程序編寫,對熟練掌握C++、對java不熟悉的讀者來說,程序讀起來相當費勁。因此本文作者將書中的23個設計模式的程序全部用visual C++6.0和STL庫進行了重新編寫和編譯。
  • 設計模式之策略模式(Java實現例子說明)
    設計模式之策略模式小王在和同事正在吹牛時,領導過來了,小王啊,你又在吹牛了?別吹了,幹點正活,下周要去春遊,你給出幾種方案。小王一聽去旅遊,屁顛屁顛地去幹活了。這個其實就是一個設計模式,叫做策略模式。策略模式(Strategy Pattern):定義一組算法,將每個算法封裝起來,並且使他們之間可以互換。策略模式是一個簡單的模式也叫做政策模式。
  • JavaScript設計模式之構造函數模式
    介紹構造函數大家都很熟悉了,不過如果你是新手,還是有必要來了解一下什麼叫構造函數的。構造函數用於創建特定類型的對象——不僅聲明了使用的對象,構造函數還可以接受參數以便第一次創建對象的時候設置對象的成員值。你可以自定義自己的構造函數,然後在裡面聲明自定義類型對象的屬性或方法。
  • 基於NCOIPcore的Chirp函數實現設計
    Chirp函數在射電天文信號的消色散處理中發揮著重要的作用,研究在FPGA中實現Chirp函數是基於FPGA的射電宇宙信號處理的重要組成部分。如圖1所示。該設計就是通過實時的改變NCO IP core的輸入頻率控制字的辦法,數控頻率輸出的辦法實現Chirp函數。
  • 巨杉資料庫SequoiaDB巨杉TechSequoiaDB 分布式事務實現原理簡介
    實現多版本並發控制常用的方式是「快照隔離(Snapshot Isolation)」技術;下面先分別介紹一下這兩個概念。事務時間間隔也可以定義為不同節點發起的事務時間之間的最小可以容忍的誤差。即如果兩個不同節點的事務時間之間相關小於事務時間間隔,即認為這兩個事務時間有誤差的情況下相等。4.2 二段提交實現巨杉資料庫對於分布式事務採用的是經典二段提交(2PC)方式實現的。
  • 軟體項目實訓及課程設計指導——如何在項目中實現日誌、事務功能
    軟體項目實訓及課程設計指導——如何在J2EE平臺項目中實現交易日誌、事務控制等功能1.1 基於面向切面AOP設計思想的系統架構設計實現交易日誌的應用示例1、面向對象OOP設計中的「開-閉」(OCP,Open-Close Principle)設計原則
  • 設計模式:單例模式
    基本概念1.1 原理單例模式可以說是所有設計模式中最簡單的一個了,這裡我們先直接給出它的概念然後再對它進行詳細的講解。單例模式就是:一個類只能有一個實例,並提供對該實例的全局訪問點。通俗地說,就是一個類只能創建一個對象,並且在程序的任何地方都能夠訪問到該對象。在某些情況下一些類只需要一個實例就夠了,我們以一個簡化的文件管理器作為例子來說明。
  • Java Singleton設計模式DEMO
    單例設計模式的實現你如何確保一個類只有一個實例?好吧,在Java中有幾種方法可以做到這一點。但所有這些都基於以下基本思想:聲明一個私有構造函數以防止其他人實例化該類。在靜態欄位/塊中的類加載期間創建類的實例,或者在靜態方法中按需創建,該靜態方法首先檢查實例是否存在,並且僅在不存在的情況下創建新實例。
  • 設計模式篇-工廠模式
    三、實現工廠模式讓我們先來實現一個工廠模式,來體會一下怎麼用工廠模式生產產品吧1、首先創建一個產品的抽象類和兩個產品public abstract class Product工廠模式的實現分為三種:簡單工廠模式、工廠方法模式和抽象工廠模式一般的文章都是將抽象工廠模式作為一種單獨的設計模式來講解,不過個人認為三種一起比較能更好的理解工廠模式的使用。
  • 函數式語言庫模式:框架是魔鬼?
    本文重點介紹了如何設計組合化的庫以及如何避免在庫設計時進行回調。Tomas倡導以庫而不是框架的方式進行開發。以下為譯文。框架 VS. 庫框架和庫有什麼區別呢?兩者的主要不同之處在於如何使用它們以及編寫什麼樣的代碼。
  • 20 個設計模式和軟體設計面試問題
    考慮到針對性和困難度,我把它們分成兩個種類,分別針對初學者和有經驗的人。對中高級別的設計模式面試問題這是一些和設計模式還有軟體設計都相關的問題。這些問題需要一些思考和經驗來回答。在大多數情況下,面試官並不是需要一個確切的答案,而是希望聽到你的想法,你對這個問題是怎麼考慮的,你能不能想通這個問題,能不能挖掘一些沒有告訴你的潛在信息。
  • 設計模式之適配器模式(Java實現)
    設計模式之適配器模式(Java實現)小王正在和美女聊天,突然接到領導電話,要讓會議室投影匯報項目情況,只能暫時離開美女去了會議室,到了會議室,發現投影儀的接口和自己電腦中的接口不匹配這個在設計模式中就是適配器模式。適配器模式(Adapter Pattern):將一個類的接口變換成客戶端所期待的另一個接口,從而是原本因為接口不匹配而無法在一起工作的兩個類能夠在一起工作。適配器模式的通用類圖如下:
  • excel函數:乘實現and函數 加實現or函數 技巧案例解讀
    前面的文章中介紹了excel中的邏輯函數,邏輯函數實現多條件判斷比使用if函數嵌套理解起來相對簡單多了。其中經常使用的邏輯函數就是and函數和or函數。今天小編給大家分享的這兩個案例就巧妙的使用「乘*」來實現and函數的功能,「+加」來實現or函數的功能。
  • 精讀《設計模式 - Bridge 橋接模式》
    橋接模式比較難理解,我會一步步還原該設計模式的思考,讓你體會這個設計模式是如何一步一步被提煉出來的。舉例子如果看不懂上面的意圖介紹,沒有關係,設計模式需要在日常工作裡用起來,結合例子可以加深你的理解,下面我準備了三個例子,讓你體會什麼場景下會用到這種設計模式。
  • 面試官:Mybatis 使用了哪些設計模式?
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫雖然我們都知道有20多個設計模式,但是大多停留在概念層面,真實開發中很少遇到,Mybatis源碼中使用了大量的設計模式,閱讀源碼並觀察設計模式在其中的應用,能夠更深入的理解設計模式。
  • 【觀點】「另類」設計模式
    內容如下:任何一個熟悉那本由四個人寫的經典的設計模式書的朋友,應該知道那本書裡的模式都是非常優雅和劃時代的。然而,不幸的是,從那些老代碼中無法提練出這些模式,因為,在出現這些模式前,大家都不會使用模式。因此,這項工作是從大量的代碼中提練出一個模式的目錄。這些模式都有充足和永恆的示例。