
Это может объяснить, почему это часто используется для отступления кода . Кодовые отступления — интересный способ изучения.
Удивительно, как работа с новыми парами дает вам новые идеи практически каждый раз.
На последнем ретрите кода, который я посетил, одна из моих пар предложила использовать шаблон Flyweight для ячеек:
Flyweight — это общий объект, который может использоваться одновременно в нескольких контекстах. Flyweight действует как независимый объект в каждом контексте — он неотличим от экземпляра объектов, которые не являются общими.
Когда вышла книга Design Patterns (которая содержит приведенную выше цитату), я помню, что у меня было много моментов ага. Было так круто увидеть все эти шаблоны, которые я использовал раньше, и, наконец, назвать их, чтобы я мог обсудить их с коллегами более эффективно!
У меня не было ага, когда я читал о весе в полете, однако. Пример, приведенный в книге о совместном использовании символьных объектов в текстовом редакторе, в то время казался немного надуманным. Однако этот пример мало чем отличается от ячеек в сетке Game of Life, поэтому я с радостью согласился с идеей моей пары исследовать применимость шаблона в этом контексте.
После того, как отступление кода было закончено, я немного подумал над этим шаблоном. (Это обычно, когда отступление кода действительно начинает окупаться.)
На самом деле мы все время используем потенциальный вес в полете: логические значения. Логический класс — это класс только с двумя экземплярами, и эти экземпляры могут легко использоваться совместно. В Java это не так: new Boolean(true) != new Boolean(true) . Однако Boolean класс предоставляет две константы, TRUE и FALSE , для экземпляров, которые вы можете использовать для совместного использования.
Это заставило меня задуматься об использовании Enum s для навесок. Большую часть времени я использую перечисления для группировки связанных, но взаимоисключающих констант, например, дней недели. Однако Enum s в Java может определять методы:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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 и не использовать условные выражения — это представить карту:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
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); } } |
Этот подход работает, но он не очень элегантен и ломается, когда число возможностей растет. Очевидно, нам нужна лучшая альтернатива.
Единственный способ избавиться от условного — избавиться от логического состояния ячейки. Это означает, что нам нужно иметь разные классы для двух экземпляров, чтобы тип неявно воплощал состояние. Это, в свою очередь, означает, что нам нужна фабрика, чтобы скрыть эти классы от клиента:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
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.
Благодаря отступлению от кода и моему партнеру я теперь знаю почему.
| Ссылка: | Игра жизни Конвея и шаблон Flyweight от нашего партнера JCG Ремона Синнема в блоге по разработке безопасного программного обеспечения . |