从功能和技术的角度来看,Conway的“生活游戏”都非常有趣。
这可以解释为什么它经常用于代码撤退 。 代码撤退是一种有趣的学习方法。
几乎每次与新对一起工作都会为您带来新见解,这真是令人惊讶。
在我参加的最后一次代码务虚会上,我的一对建议我们对单元格使用Flyweight模式 :
flyweight是一个共享对象,可以同时在多个上下文中使用。 在每个上下文中,flyweight都充当一个独立的对象-与未共享的对象实例是无法区分的。
当《 设计模式》一书(包含上面的引文)问世时,我记得有很多啊哈。 看到我以前使用过的所有这些模式,并最终为其命名,真是太酷了,这样我可以与同龄人更有效地讨论它们!
但是,当我阅读有关飞重的信息时,我并没有感到惊讶。 书中的示例在文本编辑器中共享字符对象,当时似乎有些牵强。 但是,此示例与“生命游戏”网格中的单元格并无不同,因此我很高兴地沿用了他们俩的想法,探索了该模式在这种情况下的适用性。
代码撤退结束后,我进一步考虑了该模式。 (这通常是代码撤退真正开始起作用的地方。)
实际上,我们一直在使用潜在的重量级:布尔值。 布尔值是只有两个实例的类,可以轻松共享这些实例。 在Java中,它们不是: new Boolean(true) != new Boolean(true) 。 但是, Boolean类确实为可用于共享的实例提供了两个常量TRUE和FALSE 。
那让我开始考虑将Enum用作飞行重量。 大多数时候,我使用枚举对相关但互斥的常量进行分组,例如一周中的几天。 但是,Java中的Enum可以定义方法:
public enum Cell {
ALIVE(true), DEAD(false);
private final boolean alive;
private Cell(boolean alive) {
this.alive = alive;
}
public boolean isAlive() {
return alive;
}
public Cell evolve(int numLiveNeighbors) {
boolean aliveInNextGeneration = alive
? 2 <= numLiveNeighbors && numLiveNeighbors <= 3
: numLiveNeighbors == 3;
return aliveInNextGeneration ? ALIVE : DEAD;
}
}
代码撤退的有趣部分之一是,在某些会话中,您将对工作方式有所限制。 这样的约束迫使您更具创造力,并思考超出通常使用的技术范围。
在这种情况下有趣的一个约束是不使用任何条件,例如if或switch语句或三元运算符。 该约束背后的想法是迫使您用多态替换条件语句 ,使您的程序更加面向对象。
我看到的保留当前Cell枚举而不使用条件的唯一方法是引入映射:
public enum Cell {
ALIVE(true), DEAD(false);
private final boolean alive;
private static final Map<Boolean, Map<Integer, Cell>>
NEXT = new HashMap<>();
static {
Map<Integer, Cell> dead = new HashMap<>();
dead.put(0, DEAD);
dead.put(1, DEAD);
dead.put(2, DEAD);
dead.put(3, ALIVE);
dead.put(4, DEAD);
dead.put(5, DEAD);
dead.put(6, DEAD);
dead.put(7, DEAD);
dead.put(8, DEAD);
dead.put(9, DEAD);
NEXT.put(false, dead);
Map<Integer, Cell> alive = new HashMap<>();
alive.put(0, DEAD);
alive.put(1, DEAD);
alive.put(2, ALIVE);
alive.put(3, ALIVE);
alive.put(4, DEAD);
alive.put(5, DEAD);
alive.put(6, DEAD);
alive.put(7, DEAD);
alive.put(8, DEAD);
alive.put(9, DEAD);
NEXT.put(true, alive);
}
private Cell(boolean alive) {
this.alive = alive;
}
public boolean isAlive() {
return alive;
}
public Cell evolve(int numLiveNeighbors) {
return NEXT.get(alive).get(numLiveNeighbors);
}
}
这种方法可行,但不是很优雅,并且随着可能性的增加而失效。 显然,我们需要更好的选择。
摆脱条件的唯一方法是摆脱单元格的布尔状态。 这意味着我们需要为两个实例使用不同的类,以便类型隐式体现状态。 反过来,这意味着我们需要一个工厂来向客户端隐藏这些类:
public interface Cell {
boolean isAlive();
Cell evolve(int numLiveNeighbors);
}
public class CellFactory {
private static final Map<Boolean, Cell> CELLS
= new HashMap<>();
static {
CELLS.put(false, new DeadCell());
CELLS.put(true, new AliveCell());
}
public static Cell dead() {
return cell(false);
}
public static Cell alive() {
return cell(true);
}
static Cell cell(boolean alive) {
return CELLS.get(alive);
}
}
class DeadCell implements Cell {
@Override
public boolean isAlive() {
return false;
}
@Override
public Cell evolve(int numLiveNeighbors) {
return CellFactory.cell(numLiveNeighbors == 3);
}
}
class AliveCell implements Cell {
@Override
public boolean isAlive() {
return true;
}
@Override
public Cell evolve(int numLiveNeighbors) {
return CellFactory.cell(numLiveNeighbors == 2
|| numLiveNeighbors == 3);
}
}
的确,当您查看Flyweight模式时,您会看到建议的结构包含一个flyweight工厂,该工厂创建实现普通flyweight接口的具体flyweight类的实例。
感谢代码撤退和我的合作伙伴,我现在知道为什么。
翻译自: https://www.javacodegeeks.com/2014/04/conways-game-of-life-and-the-flyweight-pattern.html