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 构建器模式优缺点分析
优点:
- 使用构建器模式,客户端并不需要知道其内部组成的细节,也不需要知道如何构建产品,客户端只需传入必要的参数请求即能获得产品。在此模式中,将创建过程与产品进行解耦,使得相同的创建过程可以创建不同的产品对象;
- 在系统中,构建器之前彼此独立,互不相关,因此在有新的需求进入系统时,整个系统能在不改变代码的基础上扩展新的功能,可以很方便地替换具体构建器或增加新的构建器,符合“开闭原则”,客户端只需使用不同的具体构建器即能获取到目标产品;
- 构建器模式能够更加精细地控制其产品的创建过程,将复杂产品的构建步骤分解到不同的方法中,更加清晰了构建过程,进而能更方便地使用程序来控制创建过程。
缺点:
- 构建器模式所创建的产品通常具有比较多的共同点,并且构成部分通常极为类似,如果产品之间的差异性较大,并且构建过程较为复杂且共同点并不是很统一时,构建器模式将不适合于本类需求,因此构建器模式的使用范围有着一定的限制,需要对具体情况做具体分析,避免盲目使用造成系统功能冗余。
- 构建器模式中,如果产品的内部变化较为复杂,可能会导致需要定义很多具体的构建者类来实现这一系列的变化,进而导致系统变得很庞大。