打个比方,假如要绘制一个国际跳棋与国际象棋棋盘,如图所示:
生成国际跳棋棋盘的棋子对象的方法代码如下:
def populate_board(self):
for x in range(0, 9, 2):
for row in range(4):
column = x + ((row + 1) % 2)
self.board[row][column] = BlackDraught()
self.board[row + 6][column] = WhiteDraught()
可以看到,这个方法根据位置的不同,需要创建黑色的棋子对象或白色的棋子对象,上述方法的实现是一种硬编码的形式,我们可以以此为基础将其实现为工厂方法。
同样生成国际象棋棋盘的棋子对象的代码如下:
def populate_board(self):
self.board[0][0] = BlackChessRook()
self.board[0][1] = BlackChessKnight()
self.board[0][2] = BlackChessBishop()
self.board[0][3] = BlackChessQueen()
self.board[0][4] = BlackChessKing()
self.board[0][5] = BlackChessBishop()
self.board[0][6] = BlackChessKnight()
self.board[0][7] = BlackChessRook()
self.board[7][0] = WhiteChessRook()
self.board[7][1] = WhiteChessKnight()
self.board[7][2] = WhiteChessBishop()
self.board[7][3] = WhiteChessQueen()
self.board[7][4] = WhiteChessKing()
self.board[7][5] = WhiteChessBishop()
self.board[7][6] = WhiteChessKnight()
self.board[7][7] = WhiteChessRook()
for column in range(8):
self.board[1][column] = BlackChessPawn()
self.board[6][column] = WhiteChessPawn()
这个方法就很能体现出硬编码的缺陷,非常的冗长,这可以通过工厂方法而避免。
再来看棋子的实现。棋子有个基类,如下:
class Piece(str):
__slots__ = ()
原本棋子只需要str表示就行,但是为了可以真实化,也就是能够判断棋盘上的是否是棋子对象,比如想用isinstance(x,Piece)判断。__slot__是为了限定类的属性,只有在__slot__内的属性才能绑定。
然后子类继承它实现各种各样的棋子类
classBlackDraught(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class,"\N{black draughts man}")
这是国际跳棋的棋子对象,继承自Piece。由于__slot__为空限定了其没有任何属性,本身棋子对象也应当是不可变的,然而我们需要标识其是什么样的棋子,那么可以通过__new__来实现,__new__是比__init__更早调用的函数,在__new__当中的参数必须为一个class,调用其会返回一个实例对象,通过重载__new__可以使用字符串来初始化它。
以下是全部的代码:
#!/usr/bin/env python3
# Copyright © 2012-13 Qtrac Ltd. All rights reserved.
# This program or module is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version. It is provided for
# educational purposes and is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
import io
import os
import sys
import tempfile
BLACK, WHITE = ("BLACK", "WHITE")
def main():
checkers = CheckersBoard()
print(checkers)
chess = ChessBoard()
print(chess)
if sys.platform.startswith("win"):
filename = os.path.join(tempfile.gettempdir(), "gameboard.txt")
with open(filename, "w", encoding="utf-8") as file:
file.write(sys.stdout.getvalue())
print("wrote '{}'".format(filename), file=sys.__stdout__)
if sys.platform.startswith("win"):
def console(char, background):
return char or " "
sys.stdout = io.StringIO()
else:
def console(char, background):
return "\x1B[{}m{}\x1B[0m".format(
43 if background == BLACK else 47, char or " ")
class AbstractBoard:
def __init__(self, rows, columns):
self.board = [[None for _ in range(columns)] for _ in range(rows)]
self.populate_board()
def populate_board(self):
raise NotImplementedError()
def __str__(self):
squares = []
for y, row in enumerate(self.board):
for x, piece in enumerate(row):
square = console(piece, BLACK if (y + x) % 2 else WHITE)
squares.append(square)
squares.append("\n")
return "".join(squares)
class CheckersBoard(AbstractBoard):
def __init__(self):
super().__init__(10, 10)
def populate_board(self):
for x in range(0, 9, 2):
for row in range(4):
column = x + ((row + 1) % 2)
self.board[row][column] = BlackDraught()
self.board[row + 6][column] = WhiteDraught()
class ChessBoard(AbstractBoard):
def __init__(self):
super().__init__(8, 8)
def populate_board(self):
self.board[0][0] = BlackChessRook()
self.board[0][1] = BlackChessKnight()
self.board[0][2] = BlackChessBishop()
self.board[0][3] = BlackChessQueen()
self.board[0][4] = BlackChessKing()
self.board[0][5] = BlackChessBishop()
self.board[0][6] = BlackChessKnight()
self.board[0][7] = BlackChessRook()
self.board[7][0] = WhiteChessRook()
self.board[7][1] = WhiteChessKnight()
self.board[7][2] = WhiteChessBishop()
self.board[7][3] = WhiteChessQueen()
self.board[7][4] = WhiteChessKing()
self.board[7][5] = WhiteChessBishop()
self.board[7][6] = WhiteChessKnight()
self.board[7][7] = WhiteChessRook()
for column in range(8):
self.board[1][column] = BlackChessPawn()
self.board[6][column] = WhiteChessPawn()
class Piece(str):
__slots__ = ()
class BlackDraught(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{black draughts man}")
class WhiteDraught(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{white draughts man}")
class BlackChessKing(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{black chess king}")
class WhiteChessKing(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{white chess king}")
class BlackChessQueen(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{black chess queen}")
class WhiteChessQueen(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{white chess queen}")
class BlackChessRook(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{black chess rook}")
class WhiteChessRook(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{white chess rook}")
class BlackChessBishop(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{black chess bishop}")
class WhiteChessBishop(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{white chess bishop}")
class BlackChessKnight(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{black chess knight}")
class WhiteChessKnight(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{white chess knight}")
class BlackChessPawn(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{black chess pawn}")
class WhiteChessPawn(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "\N{white chess pawn}")
if __name__ == "__main__":
main()
事实上,此代码示例还有个缺陷就是棋子的子类都很相似,只是类名与所含字符串不同,我们可以将这些近乎重复的代码清理掉。
现在我们上述生成棋子对象的代码采用工厂方法,工厂方法为:
def create_piece(kind, color):
if kind == "draught":
return eval("{}{}()".format(color.title(), kind.title()))
return eval("{}Chess{}()".format(color.title(), kind.title()))
# Using eval() is risky
通过format传入不同的color和kind来创建不同类型的棋子对象,然后调用eval函数执行,就会调用字符串形成的函数。当然eval是有危险性的,因为对于任何可执行函数的字符串传给eval都会执行相应的代码。现在我们以国际跳棋为例,先放出原先的生成棋子对象的代码
for x in range(0, 9, 2):
for row in range(4):
column = x + ((row + 1) % 2)
self.board[row][column] = BlackDraught()
self.board[row + 6][column] = WhiteDraught()
与之前的国际跳棋生成的棋子对象的函数作对比,现在的代码如下:
def populate_board(self):
for x in range(0, 9, 2):
for y in range(4):
column = x + ((y + 1) % 2)
for row, color in ((y, "black"), (y + 6, "white")):
self.board[row][column] = create_piece("draught",
color)
可以看到,黑白国际棋子只需一个create_piece函数就可以根据传入的参数实现不同颜色的棋子。
这个还不能看出工厂函数的威力,现在我们以国际象棋为例子,之前生成的棋子的代码为:
def populate_board(self):
self.board[0][0] = BlackChessRook()
self.board[0][1] = BlackChessKnight()
self.board[0][2] = BlackChessBishop()
self.board[0][3] = BlackChessQueen()
self.board[0][4] = BlackChessKing()
self.board[0][5] = BlackChessBishop()
self.board[0][6] = BlackChessKnight()
self.board[0][7] = BlackChessRook()
self.board[7][0] = WhiteChessRook()
self.board[7][1] = WhiteChessKnight()
self.board[7][2] = WhiteChessBishop()
self.board[7][3] = WhiteChessQueen()
self.board[7][4] = WhiteChessKing()
self.board[7][5] = WhiteChessBishop()
self.board[7][6] = WhiteChessKnight()
self.board[7][7] = WhiteChessRook()
for column in range(8):
self.board[1][column] = BlackChessPawn()
self.board[6][column] = WhiteChessPawn()
现在改成工厂方法的形式,代码为:
def populate_board(self):
for row, color in ((0, "black"), (7, "white")):
for columns, kind in (((0, 7), "rook"), ((1, 6), "knight"),
((2, 5), "bishop"), ((3,), "queen"), ((4,), "king")):
for column in columns:
self.board[row][column] = create_piece(kind, color)
for column in range(8):
for row, color in ((1, "black"), (6, "white")):
self.board[row][column] = create_piece("pawn", color)
可以看到代码简洁了很多,生成棋子的函数只需create_piece()函数即可。易于管理。
现在列出改进后的代码:
import io
import itertools
import os
import sys
import tempfile
import unicodedata
BLACK, WHITE = ("BLACK", "WHITE")
def main():
checkers = CheckersBoard()
print(checkers)
chess = ChessBoard()
print(chess)
if sys.platform.startswith("win"):
filename = os.path.join(tempfile.gettempdir(), "gameboard.txt")
with open(filename, "w", encoding="utf-8") as file:
file.write(sys.stdout.getvalue())
print("wrote '{}'".format(filename), file=sys.__stdout__)
if sys.platform.startswith("win"):
def console(char, background):
return char or " "
sys.stdout = io.StringIO()
else:
def console(char, background):
return "\x1B[{}m{}\x1B[0m".format(
43 if background == BLACK else 47, char or " ")
class AbstractBoard:
def __init__(self, rows, columns):
self.board = [[None for _ in range(columns)] for _ in range(rows)]
self.populate_board()
def populate_board(self):
raise NotImplementedError()
def __str__(self):
squares = []
for y, row in enumerate(self.board):
for x, piece in enumerate(row):
square = console(piece, BLACK if (y + x) % 2 else WHITE)
squares.append(square)
squares.append("\n")
return "".join(squares)
class CheckersBoard(AbstractBoard):
def __init__(self):
super().__init__(10, 10)
def populate_board(self):
for x in range(0, 9, 2):
for y in range(4):
column = x + ((y + 1) % 2)
for row, color in ((y, "black"), (y + 6, "white")):
self.board[row][column] = create_piece("draught",
color)
class ChessBoard(AbstractBoard):
def __init__(self):
super().__init__(8, 8)
def populate_board(self):
for row, color in ((0, "black"), (7, "white")):
for columns, kind in (((0, 7), "rook"), ((1, 6), "knight"),
((2, 5), "bishop"), ((3,), "queen"), ((4,), "king")):
for column in columns:
self.board[row][column] = create_piece(kind, color)
for column in range(8):
for row, color in ((1, "black"), (6, "white")):
self.board[row][column] = create_piece("pawn", color)
def create_piece(kind, color):
if kind == "draught":
return eval("{}{}()".format(color.title(), kind.title()))
return eval("{}Chess{}()".format(color.title(), kind.title()))
# Using eval() is risky
class Piece(str):
__slots__ = ()
for code in itertools.chain((0x26C0, 0x26C2), range(0x2654, 0x2660)):
char = chr(code)
name = unicodedata.name(char).title().replace(" ", "")
if name.endswith("sMan"):
name = name[:-4]
exec("""\
class {}(Piece):
__slots__ = ()
def __new__(Class):
return super().__new__(Class, "{}")""".format(name, char))
# Using exec() is risky
if __name__ == "__main__":
main()
之前讲过,棋子类大多都是相似的,只是名称与所含字符串不同,所以这里利用了itertools.chain()函数去迭代便利传入的‘Unicode码位’。上述传入的第一个参数为一个元组,先遍历元组返回其中的元素,然后遍历range产生的list并返回其中的元素,通过unicodedata.name()将这些元素转换为字符串并通过一些操作将其变成子类的棋子类名。char为各种棋子类里的字符串。最终通过exec(也有危险性)以及name和char创建所需各种棋子类,大大减少了代码量,只是可观性变差了。