享元模式是一种结构型设计模式,它通过共享来有效地支持大量细粒度的对象。享元模式的核心在于识别并提取对象中可以共享的部分,从而减少内存占用。下面是享元模式减少内存占用的几个关键步骤:
-
识别可共享的部分:
- 首先,分析对象结构,找出对象中哪些部分是可以共享的,哪些部分是不可共享的(即需要为每个对象独立存储的)。
-
创建享元类:
- 将可共享的部分提取出来,创建一个或多个享元类。这些享元类将包含对象的共享状态。
-
享元工厂:
- 创建一个享元工厂来管理享元对象的创建和共享。享元工厂负责检查请求的享元对象是否已经存在。如果存在,就直接返回该对象;如果不存在,就创建一个新的享元对象,并将其存储起来供后续请求使用。
-
使用享元:
- 在客户端代码中,使用享元工厂来获取享元对象。客户端不需要知道享元对象是如何创建和共享的,只需要通过享元工厂获取所需的享元对象即可。
-
分离不可共享状态:
- 对于每个具体的对象,除了共享状态外,还需要存储一些不可共享的状态。这些状态通常与享元对象一起存储,但它们是独立的,不会影响享元对象的共享。
通过这种方式,享元模式可以显著减少内存占用,因为它避免了创建大量相似对象的需要。例如,在围棋程序中,棋盘上的棋子可以作为享元对象,因为它们的颜色和形状是固定的,可以被多个棋局共享。每个棋局只需要存储棋子的位置信息,而不是棋子本身,从而节省了内存。
总结来说,享元模式通过共享可共享的部分,减少对象的创建数量,从而有效地减少了内存占用。
在开发一个网络围棋程序时,使用享元模式可以有效地减少内存占用,特别是在服务器资源有限的情况下。以下是如何使用享元模式来实现这个网络围棋程序的步骤:
1. 定义享元对象
首先,定义围棋棋子的享元对象。棋子可以是黑棋或白棋,这是可以共享的部分。每个棋子对象将包含颜色属性,这是不变的,因此可以被多个棋局共享。
class GoStone {
private final String color;
public GoStone(String color) {
this.color = color;
}
public String getColor() {
return color;
}
}
2. 创建享元工厂
创建一个享元工厂来管理棋子对象的创建和共享。这个工厂将检查请求的棋子是否已经存在,如果存在则返回该对象,否则创建一个新的棋子对象。
import java.util.HashMap;
import java.util.Map;
class GoStoneFactory {
private final Map<String, GoStone> stones = new HashMap<>();
public GoStone getStone(String color) {
if (!stones.containsKey(color)) {
stones.put(color, new GoStone(color));
}
return stones.get(color);
}
}
3. 定义棋盘和游戏逻辑
棋盘和游戏逻辑需要使用棋子对象。每个棋局只需要存储棋子的位置信息,而不是棋子对象本身。
class GoBoard {
private final GoStone[][] board;
private final int size;
public GoBoard(int size) {
this.size = size;
this.board = new GoStone[size][size];
}
public void placeStone(int row, int col, String color) {
GoStone stone = new GoStoneFactory().getStone(color);
board[row][col] = stone;
}
public GoStone getStoneAt(int row, int col) {
return board[row][col];
}
}
4. 管理游戏状态
每个玩家的游戏状态需要独立管理,但棋子对象可以共享。
class GoGame {
private final GoBoard board;
private String currentPlayer;
public GoGame() {
this.board = new GoBoard(19); // 围棋棋盘大小
this.currentPlayer = "Black";
}
public void playMove(int row, int col) {
board.placeStone(row, col, currentPlayer);
switchPlayer();
}
private void switchPlayer() {
currentPlayer = (currentPlayer.equals("Black")) ? "White" : "Black";
}
}
5. 网络通信
通过网络通信模块,允许多个玩家联机下棋。每个玩家的操作通过网络发送到服务器,服务器处理后更新棋盘状态,并广播给所有玩家。
class GoServer {
private final GoGame game;
public GoServer() {
this.game = new GoGame();
}
public void handleMove(String player, int row, int col) {
game.playMove(row, col);
// 广播更新给所有玩家
}
}
通过这种方式,服务器只需要维护一个棋子对象池,而不需要为每个棋局创建新的棋子对象,从而大大节省了内存空间。同时,由于棋子对象是共享的,这也减少了对象创建和销毁的开销,提高了程序的性能。
在网络围棋程序中,服务器需要能够处理多个玩家的请求,包括玩家的移动、游戏状态的更新以及玩家之间的通信。以下是服务器处理多个玩家请求的一般步骤:
1. 玩家连接管理
服务器需要能够接受来自多个玩家的连接请求,并为每个玩家创建一个会话或连接对象。
class PlayerSession {
private final String playerId;
private final Socket socket;
public PlayerSession(String playerId, Socket socket) {
this.playerId = playerId;
this.socket = socket;
}
public String getPlayerId() {
return playerId;
}
public Socket getSocket() {
return socket;
}
}
2. 接收和解析请求
服务器需要监听来自玩家的请求,并解析这些请求以确定玩家想要执行的操作(如放置棋子、请求游戏状态等)。
void handleClientRequest(PlayerSession session) {
try {
ObjectInputStream ois = new ObjectInputStream(session.getSocket().getInputStream());
String request = (String) ois.readObject();
// 解析请求并执行相应操作
processRequest(request, session);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
3. 执行游戏逻辑
根据解析出的请求,服务器执行相应的游戏逻辑,如更新棋盘状态、检查游戏是否结束等。
void processRequest(String request, PlayerSession session) {
if (request.startsWith("MOVE")) {
String[] parts = request.split(",");
int row = Integer.parseInt(parts[1]);
int col = Integer.parseInt(parts[2]);
game.playMove(row, col, session.getPlayerId());
broadcastGameState();
}
}
4. 广播更新
服务器需要将游戏状态的更新广播给所有连接的玩家,以确保所有玩家都能看到最新的游戏状态。
void broadcastGameState() {
try {
for (PlayerSession session : sessions) {
ObjectOutputStream oos = new ObjectOutputStream(session.getSocket().getOutputStream());
oos.writeObject(game.getGameState());
}
} catch (IOException e) {
e.printStackTrace();
}
}
5. 管理游戏状态
服务器需要维护游戏的状态,包括当前玩家、棋盘状态、游戏是否结束等。
class GoServer {
private final GoGame game;
private final List<PlayerSession> sessions = new ArrayList<>();
public GoServer() {
this.game = new GoGame();
}
public void addPlayer(PlayerSession session) {
sessions.add(session);
}
public void removePlayer(PlayerSession session) {
sessions.remove(session);
}
public void handleClientRequest(PlayerSession session) {
try {
ObjectInputStream ois = new ObjectInputStream(session.getSocket().getInputStream());
String request = (String) ois.readObject();
// 解析请求并执行相应操作
processRequest(request, session);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
private void processRequest(String request, PlayerSession session) {
if (request.startsWith("MOVE")) {
String[] parts = request.split(",");
int row = Integer.parseInt(parts[1]);
int col = Integer.parseInt(parts[2]);
game.playMove(row, col, session.getPlayerId());
broadcastGameState();
}
}
private void broadcastGameState() {
try {
for (PlayerSession session : sessions) {
ObjectOutputStream oos = new ObjectOutputStream(session.getSocket().getOutputStream());
oos.writeObject(game.getGameState());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
6. 错误处理和异常管理
服务器需要能够处理网络错误、玩家断开连接等异常情况,并确保游戏的稳定性。
通过上述步骤,服务器可以有效地处理多个玩家的请求,确保游戏的流畅运行。
下面是一个使用享元模式实现的网络围棋程序示例,该示例展示了如何通过共享围棋棋子对象来节省内存。
from typing import Dict, Tuple, List
import weakref
# 享元类:围棋棋子
class GoStone:
def __init__(self, color: str):
self.color = color # 棋子颜色,如"black"或"white"
def render(self, x: int, y: int) -> str:
"""渲染棋子,返回棋子信息"""
return f"{self.color} stone at ({x}, {y})"
# 享元工厂:管理棋子池
class GoStoneFactory:
_pool: Dict[str, GoStone] = weakref.WeakValueDictionary()
@classmethod
def get_stone(cls, color: str) -> GoStone:
"""获取棋子实例,颜色相同的棋子共享同一实例"""
if color not in cls._pool:
cls._pool[color] = GoStone(color)
return cls._pool[color]
# 围棋棋盘类
class GoBoard:
def __init__(self, size: int = 19):
self.size = size
self.board: Dict[Tuple[int, int], GoStone] = {} # 存储落子信息
def place_stone(self, x: int, y: int, color: str) -> None:
"""在指定位置落子"""
if 0 <= x < self.size and 0 <= y < self.size:
# 使用享元工厂获取棋子实例
stone = GoStoneFactory.get_stone(color)
self.board[(x, y)] = stone
print(f"Placed {stone.render(x, y)}")
else:
print("Invalid position")
def get_board_state(self) -> List[Dict]:
"""获取当前棋盘状态"""
state = []
for (x, y), stone in self.board.items():
state.append({
'position': (x, y),
'color': stone.color
})
return state
# 玩家类
class Player:
def __init__(self, name: str, color: str):
self.name = name
self.color = color
def make_move(self, board: GoBoard, x: int, y: int) -> None:
"""玩家落子"""
print(f"{self.name} ({self.color}) is placing a stone at ({x}, {y})")
board.place_stone(x, y, self.color)
# 示例用法
if __name__ == "__main__":
# 创建棋盘
board = GoBoard()
# 创建玩家
player1 = Player("Alice", "black")
player2 = Player("Bob", "white")
# 模拟对弈过程
player1.make_move(board, 9, 9) # Alice 落黑子
player2.make_move(board, 10, 9) # Bob 落白子
player1.make_move(board, 9, 10) # Alice 落黑子
player2.make_move(board, 10, 10) # Bob 落白子
# 验证棋子实例的共享
stone1 = GoStoneFactory.get_stone("black")
stone2 = GoStoneFactory.get_stone("black")
print(f"Are black stones the same instance? {stone1 is stone2}") # 应该输出True
stone3 = GoStoneFactory.get_stone("white")
print(f"Are black and white stones different instances? {stone1 is not stone3}") # 应该输出True
这个实现通过享元模式有效减少了内存占用:
GoStone类作为享元,只存储棋子的固有属性(颜色)GoStoneFactory管理棋子池,确保相同颜色的棋子只创建一个实例- 使用弱引用存储棋子实例,允许在没有外部引用时被垃圾回收
GoBoard类处理棋盘状态,每个位置只存储对享元对象的引用Player类表示玩家,负责执行落子操作
在实际网络围棋程序中,服务器可以为所有客户端共享使用同一套棋子实例,大幅降低内存占用。当多个玩家使用相同颜色的棋子时,这些棋子对象实际上是同一个实例。



被折叠的 条评论
为什么被折叠?



