C++设计模式:构建器模式

9.1 动机

在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。构建器模式提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变,进而在一种新的构建需求进入时,能够使系统保持低耦合状态,此模式遵循了开闭原则。

9.2 模式定义

将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)

9.3 案例

《我的世界》是一款沙盒类电子游戏,玩家在游戏中可以通过摧毁或创造精妙绝伦的建筑物和艺术(上题中的史莱姆案例灵感也是源于本款游戏)。在本款游戏中,建造属于自己的基地是必不可少的关键步骤,现有一设定,玩家在建造房屋需要经历如此步骤:先是砌墙,墙体的材料选择有石头以及金子;第二步是安窗户,根据玩家需要的窗户数量建造相应的窗户;第三步是安楼梯,需要注意的是,安了楼梯,必然会需要建造多层楼;第四步是盖天花板,材料选择为石头以及黑曜石;第五步为放置家具,限定石头房中仅能有桌子和椅子,而金房子中有金桌、金椅、金制电视以及金制床。现玩家需要盖一栋三层楼高的石头屋,四面窗户,还需要一栋金屋,十二面窗户,一层楼高。通过设计完成本案例需求。

类图如下

代码如下

from abc import ABCMeta


# base abstract class for house
class House(metaclass=ABCMeta):
    def __init__(self):
        self.materials = None
        self.windows = 0  # the number of windows
        self.stairs = 0  # the number of stairs
        self.ceil = None
        self.furniture = []
        self.highRise = 1  # default:1

    def outputMyHouse(self):
        print("+++++++++++++++++++++++++++++++++myHouse!++++++++++++++++++++++++++++++++")
        print("materials:", self.materials)
        print("windows:", self.windows)
        print("stairs:", self.stairs)
        print("ceil:", self.ceil)
        print("furniture:", self.furniture)
        print("highRise:", self.highRise)
        print("+++++++++++++++++++++++++++++++++myHouse!++++++++++++++++++++++++++++++++")
from abc import ABCMeta
from abc import abstractmethod

from task02.House import House


# base abstract class for houseBuilder
class HouseBuilder(metaclass=ABCMeta):
    def __init__(self):
        self.pHouse = House()

    def getResult(self) -> House:
        return self.pHouse

    @abstractmethod
    def buildWall(self):
        pass

    @abstractmethod
    def buildWindows(self):
        pass

    @abstractmethod
    def buildStairs(self) -> bool:
        pass

    @abstractmethod
    def buildCeil(self):
        pass

    @abstractmethod
    def putFurniture(self):
        pass

    @abstractmethod
    def buildHighRise(self):
        pass
from task02.House import House


class HouseDirector():
    def __init__(self, pHouseBuilder):
        self.pHouseBuilder = pHouseBuilder

    # the windows: should be a selection for user
    # the highRise: it depends the number of stairs
    def Construct(self, windowsNum) -> House:
        self.pHouseBuilder.buildWall()
        for i in range(windowsNum):
            self.pHouseBuilder.buildWindows()
        flag = self.pHouseBuilder.buildStairs()
        if flag:
            for i in range(self.pHouseBuilder.pHouse.stairs):
                self.pHouseBuilder.buildHighRise()
        self.pHouseBuilder.buildCeil()
        self.pHouseBuilder.putFurniture()
        return self.pHouseBuilder.getResult()
from task02.HouseBuilder import HouseBuilder


class PrincessHouseBuilder(HouseBuilder):

    def buildWall(self):
        self.pHouse.materials = "golden"

    def buildWindows(self):
        self.pHouse.windows += 1

    def buildStairs(self) -> bool:
        self.pHouse.stairs = 0
        return False

    def buildCeil(self):
        self.pHouse.ceil = "Obsidian"

    def putFurniture(self):
        self.pHouse.furniture.append("desk_golden")
        self.pHouse.furniture.append("chair_golden")
        self.pHouse.furniture.append("TV_golden")
        self.pHouse.furniture.append("bed_golden")

    # the big house don't need the highRise
    def buildHighRise(self):
        pass
from task02.HouseBuilder import HouseBuilder


class StoneHouseBuilder(HouseBuilder):

    def buildWall(self):
        self.pHouse.materials = "Stone"

    def buildWindows(self):
        self.pHouse.windows += 1

    def buildStairs(self) -> bool:
        self.pHouse.stairs = 2
        return True

    def buildCeil(self):
        self.pHouse.ceil = "Stone"

    def putFurniture(self):
        self.pHouse.furniture.append("desk")
        self.pHouse.furniture.append("chair")

    def buildHighRise(self):
        self.pHouse.highRise += 1

from task02.HouseDirector import HouseDirector
from task02.PrincessHouseBuilder import PrincessHouseBuilder
from task02.StoneHouseBuilder import StoneHouseBuilder

if __name__ == "__main__":
    print("run time!")

    # build a stone house
    shb = StoneHouseBuilder()
    hd = HouseDirector(shb)
    myStoneHouse = hd.Construct(4)
    myStoneHouse.outputMyHouse()

    # build a golden house
    phb = PrincessHouseBuilder()
    ph = HouseDirector(phb)
    myGoldenHouse = ph.Construct(12)
    myGoldenHouse.outputMyHouse()

模拟构建石屋

9.4 构建器模式在主流框架中的应用

①构建器模式在JDK中的应用

提起构建器模式在JDK中的应用,不难联想到StringBuilder和StringBuffer,StringBuilder是一个可变的字符序列,此类提供与StringBuffer兼容的API,用在字符串缓冲区被单个线程使用的使用,使用StringBuilder能减小在对字符串进行扩展时对时间的开销费用。StringBuilder以及StringBuilder是构建器模式的经典应用,其部分的源码如下:

通过阅览其源码可以得知,StringBuffer或者StringBuilder在使用append的方法之后依旧返回了stringBuffer或者stringBuilder对象,这不仅涉及到了构建器模式的应用,也体现了构建器模式的链式扩展应用。

构建器模式在mybatis中的应用

mybatis一款基于Java的持久层框架,经常使用mybatis的用户不难发现,其中SqlSessionFactory就是由SqlSessionFactoryBuilder利用XML或者Java编码获取资源来构建的,这也是一个经典的构建器模式的应用,将其应用的流程归纳如下图:

通过此图我们可以发现,SqlSessionFactoryBuilder便是使用了XML文件或者配置文件才能生成相应的SqlSessionFactory,在其中扮演着至关重要的角色。其中部分源码如下:

其余的构建方式基本相同,在这里不再占用多余篇幅去描述。

9.5 构建器模式的扩展分析

构建器模式最经典的扩展便是链式重构,通过链式重构我们可以使代码的可读性增强,并且减少对象的构建成本,还可能避免在构建对象时所出现的致命错误,在此通过一个小小的实验说明这种链式重构:

运行结果如下

由此可知,通过构建器模式的链式重构,不仅能完成我们所需要的对象构建操作,还能很方便地对对象进行构建上的延伸。

9.6 构建器模式优缺点分析

优点:

  • 使用构建器模式,客户端并不需要知道其内部组成的细节,也不需要知道如何构建产品,客户端只需传入必要的参数请求即能获得产品。在此模式中,将创建过程与产品进行解耦,使得相同的创建过程可以创建不同的产品对象;
  • 在系统中,构建器之前彼此独立,互不相关,因此在有新的需求进入系统时,整个系统能在不改变代码的基础上扩展新的功能,可以很方便地替换具体构建器或增加新的构建器,符合“开闭原则”,客户端只需使用不同的具体构建器即能获取到目标产品;
  • 构建器模式能够更加精细地控制其产品的创建过程,将复杂产品的构建步骤分解到不同的方法中,更加清晰了构建过程,进而能更方便地使用程序来控制创建过程。

缺点:

  • 构建器模式所创建的产品通常具有比较多的共同点,并且构成部分通常极为类似,如果产品之间的差异性较大,并且构建过程较为复杂且共同点并不是很统一时,构建器模式将不适合于本类需求,因此构建器模式的使用范围有着一定的限制,需要对具体情况做具体分析,避免盲目使用造成系统功能冗余。
  • 构建器模式中,如果产品的内部变化较为复杂,可能会导致需要定义很多具体的构建者类来实现这一系列的变化,进而导致系统变得很庞大。

  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值