Python与设计模式之创建型模式及实战

用Python学习一下设计模式,如果很枯燥的话,就强行能使用的就用一下。设计模式参考Python与设计模式-途索

1. 单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

import threading
import time

class Singleton(object):
    '''抽象单例'''
    def __new__(self, *args, **kw):
        if not hasattr(self,'_instance'):
            self._instance = super().__new__(self, *args, **kw)
        return self._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()

if __name__ == '__main__':
    threadList=[]
    for i in range(3):
        print("entity %d begin to run ... " % i)
        t = threading.Thread(target=Bus().sendData,args=("Entity_"+str(i),))
        t.start()
        threadList.append(t)
    for t in threadList:
        t.join() 

输出:

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

看到阿里号发在知乎的一篇设计模式下,有人评论python单例模式直接用模块就可以解决。

Python单例模式没必要弄一个单例类,用一个模块就很好,实例直接作为模块内的全局变量

实战

那模拟一下游戏中组团打BOSS的话,BOSS只实例一次,各个角色攻击或者治疗同一个BOSS。或许之后能成woext的文字小游戏。好吧,就正式命名成WOEXT吧。(wow text,致敬今日开的8.0,期待怀旧服)

import time

class Singleton(object):
    '''抽象单例'''
    def __new__(self, *args, **kw):
        if not hasattr(self,'_instance'):
            self._instance = super().__new__(self, *args, **kw)
        return self._instance

class Boss(Singleton):
    '''BOSS类'''
    def __new__(self):
        self.hp=100
        self._maxHp=100
        return super().__new__(self)

    def decHp(self,hurt=0):
        '''攻击或者治疗'''
        if hurt>0:
            self.hp-=hurt
            self.hp = self.hp if self.hp>0 else 0
        elif hurt<0:
            self.hp-=hurt
            self.hp = self.hp if self.hp<self._maxHp else self._maxHp

        return self.hp

    def getHp(self):
        return self.hp

class Fighter(object):
    '''人物类'''
    def __init__(self, name, hp=100, attackVal=10):
        self.name=name
        #self.hp=hp
        #self.attackVal=attackVal

    def attack(self, value=0):
        b=Boss()
        b.decHp(value)
        print("{0}| {1} -> boss, {2} {3} . [Boss hp={4}]".format(time.strftime("%M:%S",time.localtime()), self.name,'伤害' if value>=0 else '治疗',abs(value),b.getHp()))
        

if __name__ == '__main__':
    
    mt=Fighter("哀木涕")
    mt.attack(10)
    lr=Fighter("劣人")
    lr.attack(-3)
    dz=Fighter("呆呆贼")
    dz.attack(12)

Boss类继承单例类,将hp_maxHp属性在__new__中赋值,则拥有了单例属性(唯一、共享)。注:不在super().__new__(self)前赋值的话,则为普通属性。这样还没有赋予抽象单例Singleton太多职责,很单纯的只是实例化。

输出

47:57| 哀木涕 -> boss, 伤害 10 . [Boss hp=90]
47:57| 劣人 -> boss, 治疗 3 . [Boss hp=93]
47:57| 呆呆贼 -> boss, 伤害 12 . [Boss hp=81]

关于Python模块是天然的单例模式。想要实战的话:例子中的打印可以做为战斗日志单独存在,用单例模式(另一个模块文件)实现:

Log.py文件:

class WarLog(object):
    '''战斗日志'''
    def show(self,message):
        print(message)

warLog=WarLog()

导入:

from Log import warLog

和使用(将例子attack()方法中print语句改为):

message="{0}| {1} -> boss, {2} {3} . [Boss hp={4}]".format(time.strftime("%M:%S",time.localtime()), self.name,'伤害' if value>=0 else '治疗',abs(value),b.getHp())
warLog.show(message)

p.s. 想要比较两个实例是否为同一个实例,可用is比较: Boss() is Boss()

pss. 无意发现这么单例之后,Boss 类中不能使用带参数的__init__(self,x)了,卡了半天,有的说2.x到3.x这函数__new__(cls)后面参数没有的,和我这个无关,我的和别人错误不一样的原因是还重载了__new__,就没那么简单了,弄得心烦不弄了,反正只是为了单例属性用了__init__赋值之后也没用。搁置。我可只是为了练一下设计模式的啊,还想早早做出文字MT。

psss. 修正上面pss的备注,
Boss类中__new__, 参数不足。def __new__(self, *args, **kw),即可。但也没有用的。因为

class Boss(Singleton):
    '''BOSS类'''
    def __new__(self, *args, **kw):
        self.hp=100
        self._maxHp=100
        return super().__new__(self)
    def __init__(self,x):
        self.o_o=x
        pass

a=Boss('a') 
b=Boss('b')
print(a.o_o,b.o_o) #输出 b b 

这样的话,后面实例的也会覆盖掉之前的。所以init这种事无所谓的。所以,上面的参数我也不改了,不然我这里说这么多就没有意义了(万一别人也会遇到这种难找的错误呢)。上面这段备注都写得混乱,不好意思。

再补充一句,2.x到3.x继承单例之后如果没有重载__new__的话,有TypeError: object() takes no parameters错误,直接改单例中的__new__(cls)参数即可了。

参考

2. 工厂模式

工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。

简单工厂模式:省去了将工厂实例化的过程

抽象工厂模式:将每一个细致的产品都建立对应的工厂。 (提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类)

简单工厂模式

# 主食
class Burger():
    def __init__(self, name='', price=0.0):
        self.name=name
        self.price=price
        self.type='BURGER'
    def getPrice(self):
        return self.price
    def setPrice(self,price):
        self.price=price
    def getName(self):
        return self.name

class cheeseBurger(Burger):
    def __init__(self):
        super().__init__("cheese burger", 10.0)

class spicyChickenBurger(Burger):
    def __init__(self):
        super().__init__("spicy chicken burger", 15.0)

# 小吃
class Snack():
    def __init__(self, name='', price=0.0):
        self.name=name
        self.price=price
        self.type='SNACK'
    def getPrice(self):
        return self.price
    def setPrice(self, price):
        self.price = price
    def getName(self):
        return self.name

class chips(Snack):
    def __init__(self):
        super().__init__("chips", 6.0)

class chickenWings(Snack):
    def __init__(self):
        super().__init__("chicken wings", 12.0)

# 饮料
class Beverage():
    def __init__(self, name='', price=0.0):
        self.name=name
        self.price=price
        self.type='BEVERAGE'
    def getPrice(self):
        return self.price
    def setPrice(self, price):
        self.price = price
    def getName(self):
        return self.name

class coke(Beverage):
    def __init__(self):
        super().__init__("coke", 4.0)

class milk(Beverage):
    def __init__(self):
        super().__init__("milk", 5.0)

# 工厂
class SimpleFoodFactory():
    '''简单工厂模式'''
    @classmethod
    def createFood(self,foodClass):
        print(" factory produce a instance.")
        foodIns=foodClass()
        return foodIns

if  __name__=="__main__":
    cheese_burger=SimpleFoodFactory.createFood(cheeseBurger)
    print(cheese_burger.__dict__)
    chicken_wings=SimpleFoodFactory.createFood(chickenWings)
    print(chicken_wings.__dict__)
    coke_drink=SimpleFoodFactory().createFood(coke)
    print(coke_drink.__dict__)

输出:

 factory produce a instance.
{'name': 'cheese burger', 'price': 10.0, 'type': 'BURGER'}
 factory produce a instance.
{'name': 'chicken wings', 'price': 12.0, 'type': 'SNACK'}
 factory produce a instance.
{'name': 'coke', 'price': 4.0, 'type': 'BEVERAGE'}

抽象工厂模式

# 青蛙与虫子
class Frog(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return self.name
    def interact_with(self, obstacle):
        print('{} the Frog encounters {} and {}!'.format(self, obstacle, obstacle.action()))

class Bug(object):
    def __str__(self):
        return 'a bug'
    def action(self):
        return 'eats it'

class FrogWorld(object):
    def __init__(self, name):
        print(self)
        self.player_name = name
    def __str__(self):
        return '\n\n\t------ Frog World ------'
    def make_character(self):
        return Frog(self.player_name)
    def make_obstacle(self):
        return Bug()

# 术士与兽人
class Wizard(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return self.name
    def interact_with(self, obstacle):
        print('{} the Wizard battles against {} and {}!'.format(
            self, obstacle, obstacle.action()))

class Ork(object):
    def __str__(self):
        return 'an evil ork'
    def action(self):
        return 'kills it'

class WizardWorld(object):
    def __init__(self, name):
        print(self)
        self.player_name = name
    def __str__(self):
        return '\n\n\t------ Wizard World ------'
    def make_character(self):
        return Wizard(self.player_name)
    def make_obstacle(self):
        return Ork()

class GameEnvironment(object):
    '''游戏入口'''
    def __init__(self, factory):
        self.hero = factory.make_character()
        self.obstacle = factory.make_obstacle()
    def play(self):
        self.hero.interact_with(self.obstacle)

def validate_age(name):
    '''年龄检测'''
    try:
        age = input('Welcome {}. How old are you? '.format(name))
        age = int(age)
    except ValueError as err:
        print("Age {} is invalid, please try again...".format(age))
        return (False, age)
    return (True, age)

if __name__ == '__main__':
    name = input("Hello. What's your name? ")
    valid_input = False
    while not valid_input:
        valid_input, age = validate_age(name)
    game = FrogWorld if age < 18 else WizardWorld
    environment = GameEnvironment(game(name))
    environment.play()

FrogWorldWizardWorld为抽象工厂。
通过用户年龄判断执行哪个游戏。

输出

Hello. What's your name? cow
Welcome cow. How old are you? 80
        ------ Wizard World ------
cow the Wizard battles against an evil ork and kills it!
-----------------------------------------------------------
Hello. What's your name? baa
Welcome baa. How old are you? 12
        ------ Frog World ------
baa the Frog encounters a bug and eats it!

本来看到这个简书的例子看到也是小游戏的还有点小惊喜,运行以后很糟心。不影响运行的字符串里面的单词错误battlesevil, 影响运行的类名GameEnvironmentFrog。就搞不懂了,现在贴代码非要改一些东西才好防伪么?乱七八糟。再从游戏里面吐槽一下,Bug怎么可以有一个方法是eats it,这是自己的方法,抽象出来也应该像++美味风蛇++一样是eat me啊,不考虑这一层也应该是Frog
有一个killing或者Dining方法而Bugdie或者relish方法。

实战

把工厂模式加到之前的WOEXT中。

其实很虚啊,以前刚看Laravel文档不知是被那晦涩的翻译还是文档组织结构还是不太懂的设计模式虐的惨,所以一个设计模式不敢说都懂,现在只是看到工厂模式可以说这是工厂模式,但是用的时候就得理解到底什么是工厂模式了。这就是看懂和实战的区别,也是实战的意义所在。

一个简单工厂模式

import time
from Log import warLog

class Singleton(object):
    '''抽象单例'''
    def __new__(self, *args, **kw):
        if not hasattr(self,'_instance'):
            self._instance = super().__new__(self, *args, **kw)
        return self._instance

class Boss(Singleton):
    '''BOSS类'''
    def __new__(self, *args, **kw):
        self.hp=100
        self._maxHp=100
        return super().__new__(self)

    def decHp(self,hurt=0):
        '''攻击或者治疗'''
        if hurt>0:
            self.hp-=hurt
            self.hp = self.hp if self.hp>0 else 0
        elif hurt<0:
            self.hp-=hurt
            self.hp = self.hp if self.hp<self._maxHp else self._maxHp

        return self.hp

    def getHp(self):
        return self.hp

class Fighter(object):
    '''抽象人物类'''
    def __init__(self, name, hp=100, attackVal=10):
        self.name=name
        self.hp=hp
        self.attackVal=attackVal

    def attack(self):
        value=self.attackVal
        
        b=Boss()
        b.decHp(value)
        message="{0}| {1} -> boss, {2} {3} . [Boss hp={4}]".format(time.strftime("%M:%S",time.localtime()), self.name,'伤害' if value>=0 else '治疗',abs(value),b.getHp())
        warLog.show(message)

class Warrior(Fighter):
    '''战士'''
    name='战士'
    def __init__(self, name=""):
        if name: self.name=str(name)
        super().__init__(self.name, 150, 10)

class Hunter(Fighter):
    '''猎人'''
    name='猎人'
    def __init__(self, name=""):
        if name: self.name=str(name)
        super().__init__(self.name, 100, 8)

class Rogue(Fighter):
    '''盗贼'''
    name='盗贼'
    def __init__(self, name=""):
        if name: self.name=str(name)
        super().__init__(self.name, 80, 12)

class WarFactory(object):
    '''战争工厂'''
    @classmethod
    def createFighter(self, fighter, name=''):
        return fighter(name)

if __name__ == '__main__':
    
    zs=WarFactory.createFighter(Warrior, '哀木涕')
    zs.attack()
    lr=WarFactory.createFighter(Hunter, '劣人')
    lr.attack()
    dz=WarFactory.createFighter(Rogue, '呆呆贼')
    dz.attack()

输出

09:25| 哀木涕 -> boss, 伤害 10 . [Boss hp=90]
09:25| 劣人 -> boss, 伤害 8 . [Boss hp=82]
09:25| 呆呆贼 -> boss, 伤害 12 . [Boss hp=70]

其中,Fighter为抽象的工厂类,Warrior,Hunter,Rogue为具体的工厂类。不过貌似还没有直接创建人物简单呢,那就再补一个场景:需要人海战术打Boss,每个角色只能补一刀。那么调用只需改成:

while Boss().getHp()>0:
        r=random.choice([Warrior, Hunter, Rogue])
        WarFactory.createFighter(r).attack()

输出:

17:42| 战士 -> boss, 伤害 10 . [Boss hp=90]
17:42| 盗贼 -> boss, 伤害 12 . [Boss hp=78]
17:42| 战士 -> boss, 伤害 10 . [Boss hp=68]
17:42| 猎人 -> boss, 伤害 8 . [Boss hp=60]
17:42| 战士 -> boss, 伤害 10 . [Boss hp=50]
17:42| 猎人 -> boss, 伤害 8 . [Boss hp=42]
17:42| 战士 -> boss, 伤害 10 . [Boss hp=32]
17:42| 盗贼 -> boss, 伤害 12 . [Boss hp=20]
17:42| 猎人 -> boss, 伤害 8 . [Boss hp=12]
17:42| 盗贼 -> boss, 伤害 12 . [Boss hp=0]

当然现在Boss无还手之力,之后加上治疗之类的就会更有意思的。(巴特,我还是不知道自己有没有理解工厂

参考

3. 建造者模式

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

这个比较好理解啊,就是在一个类中,把需要的模块组合好。真·建造。就不放照抄的代码了,直接实例。

把萨满也加上,让他们四个角色组成一个菊爆小队。

import time
import random
from Log import warLog

class Singleton(object):
    '''抽象单例'''
    def __new__(self, *args, **kw):
        if not hasattr(self,'_instance'):
            self._instance = super().__new__(self, *args, **kw)
        return self._instance

class Boss(Singleton):
    '''BOSS类'''
    def __new__(self, *args, **kw):
        self.hp=100
        self._maxHp=100
        return super().__new__(self)

    def decHp(self,hurt=0):
        '''攻击或者治疗'''
        if hurt>0:
            self.hp-=hurt
            self.hp = self.hp if self.hp>0 else 0
        elif hurt<0:
            self.hp-=hurt
            self.hp = self.hp if self.hp<self._maxHp else self._maxHp

        return self.hp

    def getHp(self):
        return self.hp

class Fighter(object):
    '''抽象人物类'''
    def __init__(self, name, hp=100, attackVal=10):
        self.name=name
        self.hp=hp
        self.attackVal=attackVal

    def attack(self):
        value=self.attackVal
        
        b=Boss()
        b.decHp(value)
        message="{0} -> boss, {1} {2} . [Boss hp={3}]".format( self.name,'伤害' if value>=0 else '治疗',abs(value),b.getHp())
        warLog.show(message)

class Warrior(Fighter):
    '''战士'''
    name='战士'
    def __init__(self, name=""):
        if name: self.name=str(name)
        super().__init__(self.name, 150, 10)

class Hunter(Fighter):
    '''猎人'''
    name='猎人'
    def __init__(self, name=""):
        if name: self.name=str(name)
        super().__init__(self.name, 100, 8)

class Rogue(Fighter):
    '''盗贼'''
    name='盗贼'
    def __init__(self, name=""):
        if name: self.name=str(name)
        super().__init__(self.name, 80, 12)

class Shaman(Fighter):
    '''萨满'''
    name='萨满'
    def __init__(self, name=""):
        if name: self.name=str(name)
        super().__init__(self.name, 80, 6)

class WarFactory(object):
    '''战争工厂'''
    @classmethod
    def createFighter(self, fighter, name=''):
        return fighter(name)

# 建造模式,用来组队

class TeamUp(object):
    '''组队(生产)'''
    def __init__(self, teamName="小队"):
        self.teamName=teamName
        self.total=4
        self.zs=None
        self.ls=None
        self.dz=None
        self.sm=None
        self.team=[]
        warLog.show("来人啊,我们{0}要组队了".format(self.teamName))

    def enrollment(self, fighter):
        '''入队'''
        self.team.append(WarFactory.createFighter(fighter))
        warLog.show("{0} 入队一人:{1}".format(self.teamName,fighter.name))
    
    def __repr__(self):
        if len(self.team)==self.total:
            return "{0}成员:{1}, {2}, {3} 以及 {4}。".format(self.teamName,*[a.name for a in self.team])
        return "小组不足{0}人".format(self.total)

class TeamDirector(object):
    '''组队过程(步骤)'''
    def __init__(self, builder):
        self.group = builder
 
    def createTeam(self, zs, lr, dz, sm):
        '''入队人员'''
        self.group.enrollment(zs)
        self.group.enrollment(lr)
        self.group.enrollment(dz)
        self.group.enrollment(sm)
 
    def show(self):
        warLog.show(self.group)
 
if __name__ == '__main__':
    # 建造者模式 菊爆小队
    team=TeamDirector(TeamUp('【菊爆小队】'))
    team.createTeam(Warrior, Hunter, Rogue, Shaman)
    team.show()

输出:

19:15:49 来人啊,我们【菊爆小队】要组队了
19:15:49 【菊爆小队】 入队一人:战士
19:15:49 【菊爆小队】 入队一人:猎人
19:15:49 【菊爆小队】 入队一人:盗贼
19:15:49 【菊爆小队】 入队一人:萨满
19:15:49 【菊爆小队】成员:战士, 猎人, 盗贼 以及 萨满。

用到了上节的工厂特性。

4. 原型模式

用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象

其实最简单的理解还是Python原型模式这一篇

如果想根据现有对象复制出新的对象并对其修改,可以考虑原型模式

多简单,好娇柔和不造作,和那些妖艳贱货完全不一样。其他文章总是把深浅拷贝拿出来做重点说,其实从原型模式角度只是复制一份对象而已啊,深浅拷贝是其他重点了(我觉得)。修改一下来一个简单的原型模式:

#原型模式
import copy
 
class Point:
    def __init__(self, x, y, l):
        print(l)
        self.x = x
        self.y = y
        self.l = l

p1=Point(1,2,[1,2,3])
p2=copy.copy(p1) # 浅拷贝
p3=copy.deepcopy(p1) # 深拷贝

p1.x=0 # 修改 不可改变对象
p1.l.append(4) # 修改(列表)可改变对象 (浅拷贝只会复制原列表的引用,指向同一个对象,所以修改时会更改p2.l)

print(p1, p1.__dict__)
print(p2, p2.__dict__)
print(p3, p3.__dict__)

输出:

[1, 2, 3] #可以看到只执行了一次初始化
<__main__.Point object at 0x03A7AD10> {'x': 0, 'y': 2, 'l': [1, 2, 3, 4]}
<__main__.Point object at 0x03632490> {'x': 1, 'y': 2, 'l': [1, 2, 3, 4]}
<__main__.Point object at 0x03A8AB50> {'x': 1, 'y': 2, 'l': [1, 2, 3]}

这里就没有实例了,太简单了 😃
本篇代码GitHub

参考

主要参考,再次感谢

posted @ 2018-08-20 18:49  姜小豆  阅读(289)  评论(0编辑  收藏  举报