深度解密Python單例模式

Python設(shè)計模式

相關(guān)代碼已經(jīng)上傳至Github:Python_Development_Interview,大家可以收藏專題-Python的設(shè)計模式:解密+實(shí)戰(zhàn)荣病,之后會持續(xù)更新相關(guān)的設(shè)計模式抗愁。

1. 認(rèn)識單例模式

2. Python實(shí)現(xiàn)單例模式

3. 總結(jié)

認(rèn)識單例模式

1.1 單例模式含義

單例模式,也叫單子模式鲤孵,是一種常用的軟件設(shè)計模式。在應(yīng)用這個模式時辰如,單例對象的必須保證只有一個實(shí)例存在普监。許多時候整個系統(tǒng)只需要擁有一個的全局對象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為琉兜。比如在某個服務(wù)器程序中鹰椒,該服務(wù)器的配置信息存放在一個文件中,這些配置數(shù)據(jù)由一個單例對象統(tǒng)一讀取呕童,然后服務(wù)進(jìn)程中的其他對象再通過這個單例對象獲取這些配置信息漆际。這種方式簡化了在復(fù)雜環(huán)境下的配置管理。

實(shí)現(xiàn)單例模式的思路是:一個類能返回對象一個引用(永遠(yuǎn)是同一個)和一個獲得該實(shí)例的方法(必須是靜態(tài)方法夺饲,通常使用getInstance這個名稱)奸汇;當(dāng)我們調(diào)用這個方法時,如果類持有的引用不為空就返回這個引用往声,如果類保持的引用為空就創(chuàng)建該類的實(shí)例并將實(shí)例的引用賦予該類保持的引用擂找;同時我們還將該類的構(gòu)造函數(shù)定義為私有方法,這樣其他處的代碼就無法通過調(diào)用該類的構(gòu)造函數(shù)來實(shí)例化該類的對象浩销,只有通過該類提供的靜態(tài)方法來得到該類的唯一實(shí)例贯涎。

單例模式在多線程的應(yīng)用場合下必須小心使用。如果當(dāng)唯一實(shí)例尚未創(chuàng)建時慢洋,有兩個線程同時調(diào)用創(chuàng)建方法塘雳,那么它們同時沒有檢測到唯一實(shí)例的存在陆盘,從而同時各自創(chuàng)建了一個實(shí)例,這樣就有兩個實(shí)例被構(gòu)造出來败明,從而違反了單例模式中實(shí)例唯一的原則隘马。 解決這個問題的辦法是為指示類是否已經(jīng)實(shí)例化的變量提供一個互斥鎖(雖然這樣會降低效率)。

1.2 單例模式優(yōu)點(diǎn)

單例模式的優(yōu)點(diǎn):
1妻顶、由于單例模式要求在全局內(nèi)只有一個實(shí)例酸员,因而可以節(jié)省比較多的內(nèi)存空間;
2讳嘱、全局只有一個接入點(diǎn)幔嗦,可以更好地進(jìn)行數(shù)據(jù)同步控制,避免多重占用沥潭;
3邀泉、單例可長駐內(nèi)存,減少系統(tǒng)開銷叛氨。

1.3 單例模式缺點(diǎn)

單例模式的缺點(diǎn)
1呼渣、單例模式的擴(kuò)展是比較困難的棘伴;
2寞埠、賦于了單例以太多的職責(zé),某種程度上違反單一職責(zé)原則(六大原則后面會講到);
3焊夸、單例模式是并發(fā)協(xié)作軟件模塊中需要最先完成的仁连,因而其不利于測試;
4阱穗、單例模式在某種情況下會導(dǎo)致“資源瓶頸”饭冬。

1.4 單例模式應(yīng)用

單例模式的應(yīng)用舉例:
1、生成全局惟一的序列號揪阶;
2昌抠、訪問全局復(fù)用的惟一資源,如磁盤鲁僚、總線等炊苫;
3、單個對象占用的資源過多冰沙,如數(shù)據(jù)庫等侨艾;
4、系統(tǒng)全局統(tǒng)一管理拓挥,如Windows下的Task Manager唠梨;
5、網(wǎng)站計數(shù)器侥啤。

Python實(shí)現(xiàn)單例模式

2.1 多種實(shí)現(xiàn)方法

2.1.1.使用模塊

其實(shí)当叭,Python 的模塊就是天然的單例模式茬故,因?yàn)槟K在第一次導(dǎo)入時,會生成 .pyc 文件科展,當(dāng)?shù)诙螌?dǎo)入時均牢,就會直接加載 .pyc 文件,而不會再次執(zhí)行模塊代碼才睹。因此徘跪,我們只需把相關(guān)的函數(shù)和數(shù)據(jù)定義在一個模塊中,就可以獲得一個單例對象了琅攘。如果我們真的想要一個單例類垮庐,可以考慮這樣做:

singleton_by_module.py

class Singleton(object):
    def foo(self):
        pass
singleton = Singleton()

將上面的代碼保存在文件 singleton_by_module.py 中,要使用時坞琴,直接在其他文件中導(dǎo)入此文件中的對象哨查,這個對象即是單例模式的對象
test_singleton_by_module.py

from singleton_by_module import Singleton

t = Singleton()

這樣我們一旦調(diào)用到singleton_by_module.py就會產(chǎn)生一個singleton_by_module.pyc,以后我們每次調(diào)用都會直接引用這里面的代碼剧辐。

2.1.2.使用裝飾器

singleton_by_decorator.py

def Singleton(cls):
    _instance = {}
    count = 0

    def _singleton(*args, **kargs):
        nonlocal count
        if cls not in _instance:
            print(f"count: {count}: {cls.__name__} not init")
            _instance[cls] = cls(*args, **kargs)
        else:
            print(f"count: {count}: {cls.__name__} alreay init")
        count+=1
        return _instance[cls]

    return _singleton


@Singleton
class A(object):
    a = 1

    def __init__(self, x=0):
        self.x = x


a1 = A(2)
a2 = A(3)

print(f"a1 id: {id(a1)}, a1 value: {a1.x}")
print(f"a2 id: {id(a2)}, a2 value: {a2.x}")


### output
count: 0: A not init
count: 1: A alreay init
a1 id: 140536039677232, a1 value: 2
a2 id: 140536039677232, a2 value: 2

根據(jù)上面的運(yùn)行情況寒亥,我們可以發(fā)現(xiàn),當(dāng)a1被創(chuàng)建后調(diào)用的是正常的產(chǎn)生實(shí)例的過程荧关,當(dāng)a2被創(chuàng)建的時候溉奕,由于之前實(shí)例已經(jīng)被存儲下來,所以直接引用了a1的實(shí)例忍啤,所以他們的id是一樣的加勤,也就是他們引用了同一個內(nèi)存實(shí)例。

2.1.3.使用類

singleton_by_class.py

class Singleton:

    def __init__(self):
        pass

    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance


a1 = Singleton.instance()
a2 = Singleton.instance()

print(f"a1 id: {id(a1)}")
print(f"a2 id: {id(a2)}")

### output
a1 id: 140419818871776
a2 id: 140419818871776

一般情況同波,大家以為這樣就完成了單例模式鳄梅,但是這樣當(dāng)使用多線程時會存在問題

singleton_by_class_mutli_threading.py

class Singleton(object):

    def __init__(self):
        pass

    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

import threading

def task(arg):
    obj = Singleton.instance()
    print(obj)

for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()

程序執(zhí)行后,打印結(jié)果如下:

<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>
<__main__.Singleton object at 0x02C933D0>

看起來也沒有問題未檩,那是因?yàn)閳?zhí)行速度過快戴尸,如果在init方法中有一些IO操作,就會發(fā)現(xiàn)問題了冤狡,下面我們通過time.sleep模擬

我們在上面init方法中加入以下代碼:

singleton_by_class_mutli_threading_sleep.py

 def __init__(self):
        import time
        time.sleep(1)

重新執(zhí)行程序后孙蒙,結(jié)果如下

<__main__.Singleton object at 0x034A3410>
<__main__.Singleton object at 0x034BB990>
<__main__.Singleton object at 0x034BB910>
<__main__.Singleton object at 0x034ADED0>
<__main__.Singleton object at 0x034E6BD0>
<__main__.Singleton object at 0x034E6C10>
<__main__.Singleton object at 0x034E6B90>
<__main__.Singleton object at 0x034BBA30>
<__main__.Singleton object at 0x034F6B90>
<__main__.Singleton object at 0x034E6A90>

問題出現(xiàn)了!按照以上方式創(chuàng)建的單例筒溃,無法支持多線程

解決辦法:加鎖马篮!未加鎖部分并發(fā)執(zhí)行,加鎖部分串行執(zhí)行,速度降低,但是保證了數(shù)據(jù)安全

singleton_by_class_mutli_threading_lock.py

import time
import threading
class Singleton:
    _instance_lock = threading.Lock()

    def __init__(self):
        time.sleep(1)

    @classmethod
    def instance(cls, *args, **kwargs):
        with Singleton._instance_lock:
            if not hasattr(Singleton, "_instance"):
                Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance


def task(arg):
    obj = Singleton.instance()
    print(obj)
for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()
time.sleep(20)
obj = Singleton.instance()
print(obj)

打印結(jié)果如下:

<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>
<__main__.Singleton object at 0x02D6B110>

這樣就差不多了,但是還是有一點(diǎn)小問題怜奖,就是當(dāng)程序執(zhí)行時浑测,執(zhí)行了time.sleep(20)后,下面實(shí)例化對象時,此時已經(jīng)是單例模式了迁央,但我們還是加了鎖掷匠,這樣不太好,再進(jìn)行一些優(yōu)化岖圈,把intance方法讹语,改成下面的這樣就行:

@classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            with Singleton._instance_lock:
                if not hasattr(Singleton, "_instance"):
                    Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

這樣,一個可以支持多線程的單例模式就完成了

singleton_by_class_mutli_threading_safe.py

import time
import threading
class Singleton:
    _instance_lock = threading.Lock()

    def __init__(self):
        time.sleep(1)

    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            with Singleton._instance_lock:
                if not hasattr(Singleton, "_instance"):
                    Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance


def task(arg):
    obj = Singleton.instance()
    print(obj)
for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()
time.sleep(20)
obj = Singleton.instance()
print(obj)

完整代碼

這種方式實(shí)現(xiàn)的單例模式蜂科,使用時會有限制顽决,以后實(shí)例化必須通過 obj = Singleton.instance()

如果用 obj=Singleton() ,這種方式得到的不是單例

2.1.4基于new方法實(shí)現(xiàn)(推薦使用,方便)

通過上面例子导匣,我們可以知道才菠,當(dāng)我們實(shí)現(xiàn)單例時,為了保證線程安全需要在內(nèi)部加入鎖

我們知道贡定,當(dāng)我們實(shí)例化一個對象時赋访,是先執(zhí)行了類的new方法(我們沒寫時,默認(rèn)調(diào)用type.new)缓待,實(shí)例化對象蚓耽;然后再執(zhí)行類的init方法,對這個對象進(jìn)行初始化旋炒,所有我們可以基于這個步悠,實(shí)現(xiàn)單例模式

singleton_by_new.py

import threading
class Singleton:
    _instance_lock = threading.Lock()

    def __init__(self):
        pass


    def __new__(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            with Singleton._instance_lock:
                if not hasattr(Singleton, "_instance"):
                    Singleton._instance = super(Singleton,cls).__new__(cls,*args, **kwargs)  
        return Singleton._instance

obj1 = Singleton()
obj2 = Singleton()
print(obj1,obj2)

def task(arg):
    obj = Singleton()
    print(obj)

for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()

打印結(jié)果如下:

<__main__.Singleton object at 0x038B33D0> <__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>
<__main__.Singleton object at 0x038B33D0>

采用這種方式的單例模式,以后實(shí)例化對象時国葬,和平時實(shí)例化對象的方法一樣 obj = Singleton()

2.1.5.基于metaclass方式實(shí)現(xiàn)

相關(guān)知識

"""
1.類由type創(chuàng)建贤徒,創(chuàng)建類時芹壕,type的init方法自動執(zhí)行汇四,類() 執(zhí)行type的 call方法(類的new方法,類的init方法)
2.對象由類創(chuàng)建,創(chuàng)建對象時踢涌,類的init方法自動執(zhí)行通孽,對象()執(zhí)行類的 call 方法
"""

class Foo:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        pass

obj = Foo()
# 執(zhí)行type的 __call__ 方法,調(diào)用 Foo類(是type的對象)的 __new__方法睁壁,用于創(chuàng)建對象背苦,然后調(diào)用 Foo類(是type的對象)的 __init__方法,用于對對象初始化潘明。

obj()    # 執(zhí)行Foo的 __call__ 方法

元類的使用
metaclass_ex.py

class SingletonType(type):
    def __init__(self,*args,**kwargs):
        super(SingletonType,self).__init__(*args,**kwargs)

    def __call__(cls, *args, **kwargs): # 這里的cls行剂,即Foo類
        print('cls',cls)
        obj = cls.__new__(cls,*args, **kwargs)
        cls.__init__(obj,*args, **kwargs) # Foo.__init__(obj)
        return obj

class Foo(metaclass=SingletonType): # 指定創(chuàng)建Foo的type為SingletonType
    def __init__(self,name):
        self.name = name
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

obj = Foo('xx')

實(shí)現(xiàn)單例模式

singleton_by_metaclass.py

import threading

class SingletonType(type):
    _instance_lock = threading.Lock()
    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            with SingletonType._instance_lock:
                if not hasattr(cls, "_instance"):
                    cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)
        return cls._instance

class Foo(metaclass=SingletonType):
    def __init__(self,name):
        self.name = name


obj1 = Foo('name')
obj2 = Foo('name')
print(obj1,obj2)

2.2 實(shí)例分析

總線是計算機(jī)各種功能部件或者設(shè)備之間傳送數(shù)據(jù)、控制信號等信息的公共通信解決方案之一∏担現(xiàn)假設(shè)有如下場景:某中央處理器(CPU)通過某種協(xié)議總線與一個信號燈相連厚宰,信號燈有64種顏色可以設(shè)置,中央處理器上運(yùn)行著三個線程,都可以對這個信號燈進(jìn)行控制铲觉,并且可以獨(dú)立設(shè)置該信號燈的顏色澈蝙。抽象掉協(xié)議細(xì)節(jié)(用打印表示),如何實(shí)現(xiàn)線程對信號等的控制邏輯撵幽。
加線程鎖進(jìn)行控制灯荧,無疑是最先想到的方法,但各個線程對鎖的控制盐杂,無疑加大了模塊之間的耦合逗载。下面,我們就用設(shè)計模式中的單例模式链烈,來解決這個問題撕贞。

代碼如下:

import threading
import time
#這里使用方法__new__來實(shí)現(xiàn)單例模式
class Singleton(object):#抽象單例
    def __new__(cls, *args, **kw):
        if not hasattr(cls, '_instance'):
            orig = super(Singleton, cls)
            cls._instance = orig.__new__(cls, *args, **kw)
        return cls._instance
#總線
class Bus(Singleton):
    lock = threading.RLock()
    def sendData(self,data):
        self.lock.acquire()
        time.sleep(3)
        print "Sending Signal Data...",data
        self.lock.release()
#線程對象,為更加說明單例的含義测垛,這里將Bus對象實(shí)例化寫在了run里
class VisitEntity(threading.Thread):
    my_bus=""
    name=""
    def getName(self):
        return self.name
    def setName(self, name):
        self.name=name
    def run(self):
        self.my_bus=Bus()
        self.my_bus.sendData(self.name)

if  __name__=="__main__":
    for i in range(3):
        print "Entity %d begin to run..."%i
        my_entity=VisitEntity()
        my_entity.setName("Entity_"+str(i))
        my_entity.start()

運(yùn)行結(jié)果如下:
Entity 0 begin to run...
Entity 1 begin to run...
Entity 2 begin to run...
Sending Signal Data... Entity_0
Sending Signal Data... Entity_1
Sending Signal Data... Entity_2
在程序運(yùn)行過程中捏膨,三個線程同時運(yùn)行(運(yùn)行結(jié)果的前三行先很快打印出來),而后分別占用總線資源(后三行每隔3秒打印一行)食侮。雖然看上去總線Bus被實(shí)例化了三次号涯,但實(shí)際上在內(nèi)存里只有一個實(shí)例。

總結(jié)

因?yàn)閱卫J皆谠O(shè)計模式中算是最基礎(chǔ)且最簡單的一個模式锯七,因此在一般初級面試的時候链快,面試官都會通過這個問題來考察,一個很重要的原因是單例模式實(shí)現(xiàn)方法多種且優(yōu)化的方式也有很多眉尸,所以也很能考察應(yīng)聘者的水平域蜗,所以,大家要好好學(xué)這個最基礎(chǔ)的設(shè)計模式霸牖霉祸!另外,在Java中單例模式常說的飽漢*餓漢*模式袱蜡,其實(shí)和Python中的利用__new__和利用class來創(chuàng)建是一樣的丝蹭,也就是在什么時候創(chuàng)建實(shí)例的區(qū)別。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坪蚁,一起剝皮案震驚了整個濱河市奔穿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌敏晤,老刑警劉巖贱田,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嘴脾,居然都是意外死亡男摧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來彩倚,“玉大人筹我,你說我怎么就攤上這事》耄” “怎么了蔬蕊?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哥谷。 經(jīng)常有香客問我岸夯,道長,這世上最難降的妖魔是什么们妥? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任猜扮,我火速辦了婚禮,結(jié)果婚禮上监婶,老公的妹妹穿的比我還像新娘旅赢。我一直安慰自己,他們只是感情好惑惶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布煮盼。 她就那樣靜靜地躺著,像睡著了一般带污。 火紅的嫁衣襯著肌膚如雪僵控。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天鱼冀,我揣著相機(jī)與錄音报破,去河邊找鬼。 笑死千绪,一個胖子當(dāng)著我的面吹牛充易,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翘紊,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蔽氨,長吁一口氣:“原來是場噩夢啊……” “哼藐唠!你這毒婦竟也來了帆疟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宇立,失蹤者是張志新(化名)和其女友劉穎踪宠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妈嘹,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柳琢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柬脸。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡他去,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出倒堕,到底是詐尸還是另有隱情灾测,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布垦巴,位于F島的核電站媳搪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏骤宣。R本人自食惡果不足惜秦爆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望憔披。 院中可真熱鬧等限,春花似錦、人聲如沸芬膝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蔗候。三九已至怒允,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锈遥,已是汗流浹背纫事。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留所灸,地道東北人丽惶。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像爬立,于是被迫代替她去往敵國和親钾唬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355