Python园地

Python设计模式巡礼1:创建型模式

2019-08-09  本文已影响0人  小可哥哥V

本文参考《设计模式——可复用面向对象软件基础》

完整代码请看: https://github.com/ubwshook/PythonStudy/tree/master/design_patterns

谈及设计模式就不得不的提起《设计模式——可复用面向对象软件基础》,这是一部设计模式的经典书籍,它归纳了常用的23中设计模式。设计模式使人们可以更加简单和方便地复用成功的设计和体系结构。 该书中主要是采用C++作为示例语言,虽然设计模式是一种编程思想,但是不同语言所提供的特性会让设计模式的实现非常的不同。Python的一等函数以及其他动态特性都会使设计模式的实现产生变化。

我们将对书中提到设计模式使用Python语言进行分析重构,所使用的例子尽可能与书中相同,下面让我们一起开始Python设计模式巡礼的第一站——创建型模式!

依据设计模式的目的可分为 创建型(Creational)、结构型(Structural)、行为型(Beaviroal) 三种。创建型模式与对象的创建有关; 结构型模式处理类或对象的组合; 行为型对类或对象怎么交互和怎样分配职责进行描述。

0x0 . 迷宫问题描述以及初始Dome

迷宫使我们都玩过的一种游戏,我们现在来关注一个迷宫怎么创建。我们将一个迷宫定义为一系列的房间,一个房间要知道他的邻居;可能的邻居要么是另一个房间,要么是一堵墙、或者是到另一个房间的一扇门。类Room、Door、Wall定义了我们所有的例子中使用到的构建。

首先我们定义一个MapSite类:

class MapSite(object):
    """
    类MapSite是所有迷宫组件的公共抽象类。我了简化例子,MapSite仅定义了一个操作Enter,它取决于你在进入什么。
    如果你进入一个房间,那么你的位置会发生改变。如果你试图进入一扇门,那么这两件事中就有一件会发生:如果们是开
    着的,你进入另一个房间。如果门是关着的,那么你就会碰壁。
    """
    def enter(self):
        """
        Enter是为更加复杂的有些操作提供了一个简单基础。例如,如果你在一个房间中说“向东走”,
        游戏只能确定直接在东边是哪一个MapSite并对它调用Enter。特定子类的Enter操作将计算
        出你的位置是发生改变,还是碰壁。
        :return:
        """
        return

Room是MapSite的一个子类,用来定义房间:

class Room(MapSite):
    """
    Room是MapSite的一个具体子类,而MapSite定义了迷宫中构件之间的主要关系。
    Room是指向其他MapSite对象的引用,并保存一个房间号,用来标识迷宫中的房间
    """
    def __init__(self, room_no):
        super(Room, self).__init__()
        self._room_no = room_no
        self._sides = {'East': None, 'West': None, 'North': None, 'South': None }

    def get_side(self, direction):
        return self._sides[direction]

    def set_side(self, direction, map_site):
        if direction not in ['East', 'West', 'North', 'South']:
            print("Dirction is invalid!")

        self._sides[direction] = map_site

    def get_no(self):
        return self._room_no

Room中初始化的时候会设置房间号。我们可以使用set_side和get_side来设置或获取房间某一面的对象。

定义Wall类,Wall来表示墙,墙这里比较简单没有额外的东西:

class Wall(MapSite):
    """
    这个类描述的是墙,在demo中是比较简单的
    """
    def __init__(self):
        super(Wall, self).__init__()

定义Door类,Door表示一扇门,需要知道门联通了那两个房间:

class Door(MapSite):
    """
    这个类描述的是门这个对象
    """
    def __init__(self, room1: Room, room2: Room):
        super(Door, self).__init__()
        self._room1 = room1
        self._room2 = room2
        self._is_open = 0

    def other_side_from(self, room):
        """
        获取一个房间另一面的房间
        :param room:
        :return: 输入room另一面的room
        """
        if room.get_no() == self._room1.get_no():
            return self._room2
        elif room.get_no() == self._room2.get_no() :
            return self._room1
        else:
            print("Room is wrong!")
            return None

定义一个Maze类,用来表示房间的集合。可以从中按照房间号去获取room对象:

class Maze(object):
    """
    迷宫中房间集合的类,可以向迷宫中添加或者获取房间
    """
    def __init__(self):
        self.rooms = {}

    def add_room(self, room: Room):
        self.rooms[room.get_no()] = room

    def get_room(self, room_no):
        return self.rooms[room_no]

我们的最终目标是为迷宫游戏创建迷宫,这里有一个迷宫游戏类MazeGame,其中crete_maze函数是创建一个迷宫的方法, 简单创建一个迷宫如下, 创建两个房间,两个房间用door连接,这是一个非常简单迷宫。

class MazeGame(object):
    """
    Maze游戏类,没有完整去实现功能,我们主要关注创建型模式,所以这里只描述创建函数的实现。
    """
    @staticmethod
    def create_maze():
        maze = Maze()
        room1 = Room(1)
        room2 = Room(2)
        door = Door(room1, room2)

        maze.add_room(room1)
        maze.add_room(room2)

        room1.set_side('North', Wall())
        room1.set_side('East', door)
        room1.set_side('South', Wall())
        room1.set_side('West', Wall())

        room2.set_side('North', Wall())
        room2.set_side('East', Wall())
        room2.set_side('South', Wall())
        room2.set_side('West', door)

        return maze

对于一个仅有两个房间的迷宫来说,这套代码是相当的复杂的。而它真正的问题是不灵活,它对迷宫的布局采用了硬编码,这意味着改变布局,就要改变这个成员函数。

考虑另一种情况,你想设计一个试了魔法的迷宫,这里面的Room、Wall、Door等组件都与基础的不同,那么怎样跟容易改变create_maze以让它用这些新类型的对象创建迷宫。

创建型的设计模式将根据场景的不同进行实现。

0x1 . ABSTRACT FACTORY(抽象工厂)

1.意图

提供一个创建一系列相关或者相互依赖对象的接口,而无需指定它们具体的类。

2.别名

Kit

3.适用性:

4.实现概要

将工厂作为单件,为创建函数设计接口,用于接收工厂实例为创建参数。工厂类可以被继承,从而实现使用不同组件来进行创建。

5.效果:

5.代码例示:

依然是迷宫问题,我们先设计一个MazeFactory,这个工厂类将提供各个组件的创建方法:

class MazeFactory(object):
    """
    工厂类,定义各个组件如何生成
    可以被覆写,定制不同工厂类
    """
    def make_maze(self):
        return Maze()

    def make_wall(self):
        return Wall()

    def make_door(self, room1: Room, room2: Room):
        return Door(room1, room2)

    def make_room(self, room_no: int):
        return Room(room_no)

为了使用factory我们的MazeFactory的create_maze方法将factory作为参数,进行迷宫的创建:

class MazeGame(object):
    """
    Maze游戏类, 这里设计的create_maze函数可以接收工厂对象进行类型初始化。
    """
    def create_maze(self, factory):
        maze = factory.make_maze()
        room1 = factory.make_room(1)
        room2 = factory.make_room(2)
        door = factory.make_door(room1, room2)

        maze.add_room(room1)
        maze.add_room(room2)

        room1.set_side('North', factory.make_wall())
        room1.set_side('East', door)
        room1.set_side('South', factory.make_wall())
        room1.set_side('West', factory.make_wall())
        room2.set_side('North', factory.make_wall())
        room2.set_side('East', factory.make_wall())
        room2.set_side('South', factory.make_wall())
        room2.set_side('West', door)
        
        return maze()

我们可以设计自己的工程类,从而定制自己的迷宫组件,比如我想要将迷宫的组件改为施加魔法的门或房间的时候,我就可以继承MazeFactory类,覆写里面生成组件的方法。

class EnchantedMazeFactory(MazeFactory):
    """
    用于创建施加了魔法迷宫的工厂类,可以生成施加魔法的
    """
    def make_door(self, room1: Room, room2: Room):
        return EnchantedDoor(room1, room2)

    def make_room(self, room_no: int):
        return EnchantedRoom(room_no)

去创建一个施加了魔法的迷宫就变得很容易:

# 创建一个施加了魔法的迷宫
game = MazeGame()
game.create_maze(EnchantedMazeFactory())

0x2 . BUILDER(生成器)——对象创建型模型

1.意图

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

2.适用性

3.实现概要

builder会把组件的构造封装在自己类内部,对外不体现构建过程,只需要不断生成组件,组件之间的联系,也被封装在builder内部,使用者只需要调用builder的方法去生成即可。

4.效果

5.代码示例:

首先我们创建一定抽象类来定义一个builder必须要实现的方法,任何构造Maze的类都必须继承它并实现对应的方法:

class MazeBuilder(object, metaclass=ABCMeta):
    """
    迷宫生成器的抽象类,用于定义迷宫生成器必须实现的方法
    """
    @abstractmethod
    def build_maze(self):
        pass

    @abstractmethod
    def build_room(self, room_no: int):
        pass

    @abstractmethod
    def build_door(self, room1_no: int, room2_no: int):
        pass

    @abstractmethod
    def get_maze(self):
        pass

接下来我们来具体实现一个builder,这里我们把它称为标准生成器:

class StandardMazeBuilder(MazeBuilder):
    """
    一个标准迷宫生成器,继承于Mazebuilder
    """
    def __init__(self):
        """
        初始化一个字典用于存放迷宫中的房间
        """
        self._current_maze = {}

    def build_maze(self):
        """
        每次调用创建迷宫就会初始化迷宫房间字典
        :return:
        """
        self._current_maze = {}

    def build_room(self, room_no):
        """
        创建一个房间,并将其加入迷宫房间字典中
        :param room_no: 房间编号
        :return:
        """
        if room_no not in self._current_maze.keys():
            room = Room(room_no)
            self._current_maze[room_no] = room

            for direction in DIRECTIONS:
                room.set_side(direction, Wall())

    def build_door(self, room1_no, room2_no):
        """
        为两个房间之间创建一道门,通过_common_wall确定门所在的方位
        :param room1_no: 房间1编号
        :param room2_no: 房间2编号
        :return:
        """
        room1 = self._current_maze[room1_no]
        room2 = self._current_maze[room2_no]
        door = Door(room1, room2)

        room1.set_side(self._common_wall(room1, room2), door)
        room2.set_side(self._common_wall(room2, room1), door)

    def get_maze(self):
        """
        返回迷宫信息
        :return: 返回迷宫字典
        """
        return self._current_maze

    def _common_wall(self, room1: Room, room2: Room):
        """
        假设迷宫宽度为8个房间,如果相邻或者在迷宫中模8相等,才能获得方位。
        :param room1: 房间1编号
        :param room2: 房间2编号
        :return:
        """
        room1_no = room1.get_no()
        room2_no = room2.get_no()

        if room2_no == room1_no + 1 and room1_no % 8 != 0:
            return 'East'

        if room1_no == room2_no + 1 and room2_no % 8 != 0:
            return 'West'

        if room1_no % 8 == room2_no % 8 and room1_no // 8 == room2_no // 8 + 1:
            return 'North'

        if room1_no % 8 == room2_no % 8 and room2_no // 8 == room1_no // 8 + 1:
            return 'South'

        return None

StandardMazeBuilder继承了MazeBuilder并实现了抽象接口方法,整个maze的构建都在类的内部进行,客户只需要添加组件,builder会为用户创建迷宫:

class MazeGame(object):
    """
    迷宫游戏类,这里仅仅实现创建迷宫的方法
    """
    def create_maze(self, builder):
        builder.build_maze()

        builder.build_room(1)
        builder.build_room(2)
        builder.build_door(1, 2)

        return builder.get_maze()

0x3 . FACTORY METHOD(工厂方法)

1.意图:

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。

2.别名:

虚构造器(virtual Constructor)

3.适用性:

4.效果:

5.实现概要

通俗的说,就是抽象类方法的应用,继承+覆写某些函数。

6.代码示例:

工厂方法模式会再MazeGame类内部定义各组件创建方法,并在创建迷宫函数create_maze中去调用:

class MazeGame(object):
    def make_maze(self):
        return Maze()

    def make_room(self, room_no):
        return Room(room_no)

    def make_door(self, room1: Room, room2: Room):
        return Door(room1, room2)

    def make_wall(self):
        return Wall()

    def create_maze(self):
        maze = self.make_maze()
        room1 = self.make_room(1)
        room2 = self.make_room(2)
        door = self.make_door(room1, room2)

        maze.add_room(room1)
        maze.add_room(room2)

        room1.set_side('North', self.make_wall())
        room1.set_side('East', door)
        room1.set_side('South', self.make_wall())
        room1.set_side('West', self.make_wall())

        room2.set_side('North', self.make_wall())
        room2.set_side('East', self.make_wall())
        room2.set_side('South', self.make_wall())
        room2.set_side('West', door)
        
        return maze

如果想要设计一种具有带炸弹房间和被炸毁的墙的迷宫,需要继承MazeGame()类覆写其中的创建函数。

class BombedMazeGame(MazeGame):
    def make_wall(self):
        return BoomedWall()

    def make_room(self, room_no):
        return RoomWithBomb

0x4 . PROTOTYPE(原型)

1.意图

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

2.适用性:

3.效果

4.实现概要

为每个组件都设置原型,在创建时复制原型即可。

5.代码示例:

我们将构造一个MazePrototypeFactory类,它是一个使用原型模式的工厂类,其中的创建组件的方法均是对原型的copy

class MazePrototypeFactory(MazeFactory):
    """
    使用原型模式的抽象工厂
    """
    def __init__(self, room, wall, door, maze):
        """
        初始化函数设置原型
        :param room: 房间原型
        :param wall: 墙壁原型
        :param door: 门原型
        :param maze: 迷宫原型
        """
        self._prototype_maze = maze
        self._prototype_room = room
        self._prototype_wall = wall
        self._prototype_door = door

    def make_wall(self):
        return deepcopy(self._prototype_wall)

    def make_room(self, room_no):
        """
        利用原型构建房间,这里使用的原型,需要调用初始化之后才能使用,这与之前不同。
        :param room_no: 房间号
        :return:
        """
        room = deepcopy(self._prototype_room)
        room.init(room_no)
        return room

    def make_door(self, room1, room2):
        door = deepcopy(self._prototype_door)
        door.init(room1, room2)
        return door

    def make_make(self):
        return deepcopy(self._prototype_maze)

可以看出其中room、door与之前不同,增加了额外的初始化,所以原先的类定义也有所修改:

class Room(MapSite):
    """
    Room是MapSite的一个具体子类,而MapSite定义了迷宫中构件之间的主要关系。
    Room是指向其他MapSite对象的引用,并保存一个房间号,用来标识迷宫中的房间
    """
    def __init__(self):
        super(Room, self).__init__()
        self._room_no = None
        self._sides = {}
        for direction in DIRECTIONS:
            self._sides[direction] = None

    def init(self, room_no):
        self._room_no = room_no

    def get_side(self, direction):
        return self._sides[direction]

    def set_side(self, direction, map_site):
        if direction not in ['East', 'West', 'North', 'South']:
            print("Dirction is invalid!")

        self._sides[direction] = map_site

    def get_no(self):
        return self._room_no


class Door(MapSite):
    """
    这个类描述的是门这个对象
    """
    def __init__(self):
        super(Door, self).__init__()
        self._room1 = None
        self._room2 = None
        self._is_open = 0

    def init(self, room1, room2):
        self._room1 = room1
        self._room2 = room2

    def other_side_from(self, room):
        """
        获取一个房间另一面的房间
        :param room:
        :return: 输入room另一面的room
        """
        if room.get_no() == self._room1.get_no():
            return self._room2
        elif room.get_no() == self._room2.get_no():
            return self._room1
        else:
            print("Room is wrong!")
            return None

下面我们就用原型工程类,来建立一个迷宫:

game = MazeGame()
# 为工厂添加原型
factory = MazePrototypeFactory(Room(), Wall(), Door(), Maze())
maze = game.create_maze(factory)

0X5 SINGLETON(单例模式)

1.意图

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

2.适用性:

3.效果

4.代码示例

这里我们不再使用迷宫作为讨论对象,在《python cookbook》中提供了非常易用的Singleton类, 只要继承它, 就会成为单例。

# python 3 代码实现
class Singleton(type):
    def __init__(self, *args, **kwargs):
        self.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            # 如果 __instance 不存在,创建新的实例
            self.__instance = super().__call__(*args, **kwargs)
            return self.__instance
        else:
            # 如果存在,直接返回
            return self.__instance


class Spam(metaclass=Singleton):
    def __init__(self):
        print('Creating Spam')


a = Spam()
b = Spam()

print(a is b)  # 这里输出为 True

这里通过元类(metaclass)实现了三件事:

这个Singleton的元类使用了__call__方法使其能够模拟函数的行为, 例子中我们构造了一个Singleton元类,并使用call方法使其能够模拟函数的行为。构造类 Spam 时,将其元类设为Singleton,那么创建类对象 Spam 时,会走到Singleton的__call__方法中。
Singleton元类仅仅会初始化一次,这样继承它的类,就具有了单例模式。

单例模式还有其他一些实现方法,我们不在这里一一赘述。

上一篇下一篇

猜你喜欢

热点阅读