Complex classes like QuadTree often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use QuadTree, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
7 | class QuadTree |
||
8 | { |
||
9 | const Q_TOP_LEFT = 0; |
||
10 | const Q_TOP_RIGHT = 1; |
||
11 | const Q_BOTTOM_LEFT = 2; |
||
12 | const Q_BOTTOM_RIGHT = 3; |
||
13 | |||
14 | /** @var int */ |
||
15 | protected $level; |
||
16 | /** @var int */ |
||
17 | protected $maxObjects; |
||
18 | /** @var int */ |
||
19 | protected $maxLevels; |
||
20 | /** @var ArrayCollection */ |
||
21 | protected $objects; |
||
22 | /** @var \SixtyNine\DataTypes\Box */ |
||
23 | protected $bounds; |
||
24 | /** @var bool */ |
||
25 | protected $isSplit = false; |
||
26 | /** @var array QuadTree[] */ |
||
27 | protected $nodes; |
||
28 | |||
29 | /** |
||
30 | * @param Box $bounds |
||
31 | * @param int $level |
||
32 | * @param int $maxObjects |
||
33 | * @param int $maxLevels |
||
34 | */ |
||
35 | 20 | public function __construct(Box $bounds, $level = 0, $maxObjects = 10, $maxLevels = 10) |
|
43 | |||
44 | /** @SuppressWarnings(PHPMD.ShortVariable) */ |
||
45 | 7 | public function split() |
|
46 | { |
||
47 | 7 | $this->isSplit = true; |
|
48 | |||
49 | 7 | $subWidth = (int)($this->bounds->getWidth() / 2); |
|
50 | 7 | $subHeight = (int)($this->bounds->getHeight() / 2); |
|
51 | |||
52 | 7 | $x = $this->bounds->getX(); |
|
53 | 7 | $y = $this->bounds->getY(); |
|
54 | |||
55 | 7 | $this->nodes = array(); |
|
56 | 7 | $this->nodes[self::Q_TOP_LEFT] = new Quadtree(new Box($x, $y, $subWidth, $subHeight), $this->level + 1, $this->maxObjects, $this->maxLevels); |
|
57 | 7 | $this->nodes[self::Q_TOP_RIGHT] = new Quadtree(new Box($x + $subWidth, $y, $subWidth, $subHeight), $this->level + 1, $this->maxObjects, $this->maxLevels); |
|
58 | 7 | $this->nodes[self::Q_BOTTOM_LEFT] = new Quadtree(new Box($x, $y + $subHeight, $subWidth, $subHeight), $this->level + 1, $this->maxObjects, $this->maxLevels); |
|
59 | 7 | $this->nodes[self::Q_BOTTOM_RIGHT] = new Quadtree(new Box($x + $subWidth, $y + $subHeight, $subWidth, $subHeight), $this->level + 1, $this->maxObjects, $this->maxLevels); |
|
60 | 7 | } |
|
61 | |||
62 | /** |
||
63 | * @param Box $box |
||
64 | * @return bool |
||
65 | */ |
||
66 | 17 | protected function isInTopQuadrant(Box $box) |
|
67 | { |
||
68 | 17 | $hMidpoint = $this->bounds->getCenter()->getY(); |
|
69 | 17 | return $box->getY() <= $hMidpoint && $box->getY() + $box->getHeight() <= $hMidpoint; |
|
70 | } |
||
71 | |||
72 | /** |
||
73 | * @param Box $box |
||
74 | * @return bool |
||
75 | */ |
||
76 | 17 | protected function isInBottomQuadrant(Box $box) |
|
77 | { |
||
78 | 17 | $hMidpoint = $this->bounds->getCenter()->getY(); |
|
79 | 17 | return $box->getY() >= $hMidpoint; |
|
80 | } |
||
81 | |||
82 | /** |
||
83 | * @param Box $box |
||
84 | * @return bool |
||
85 | */ |
||
86 | 17 | protected function isInLeftQuadrant(Box $box) |
|
87 | { |
||
88 | 17 | $vMidpoint = $this->bounds->getCenter()->getX(); |
|
89 | 17 | return $box->getX() <= $vMidpoint && $box->getX() + $box->getWidth() <= $vMidpoint; |
|
90 | } |
||
91 | |||
92 | /** |
||
93 | * @param Box $box |
||
94 | * @return bool |
||
95 | */ |
||
96 | 17 | protected function isInRightQuadrant(Box $box) |
|
97 | { |
||
98 | 17 | $vMidpoint = $this->bounds->getCenter()->getX(); |
|
99 | 17 | return $box->getX() >= $vMidpoint; |
|
100 | } |
||
101 | |||
102 | /** |
||
103 | * @param Box $box |
||
104 | * @return int |
||
105 | */ |
||
106 | 17 | public function getIndex(Box $box) |
|
107 | { |
||
108 | 17 | $topQuadrant = $this->isInTopQuadrant($box); |
|
109 | 17 | $bottomQuadrant = $this->isInBottomQuadrant($box); |
|
110 | 17 | $leftQuadrant = $this->isInLeftQuadrant($box); |
|
111 | 17 | $rightQuadrant = $this->isInRightQuadrant($box); |
|
112 | |||
113 | switch (true) { |
||
114 | 17 | case $leftQuadrant && $topQuadrant: |
|
115 | 8 | return self::Q_TOP_LEFT; |
|
116 | 15 | case $rightQuadrant && $topQuadrant: |
|
117 | 7 | return self::Q_TOP_RIGHT; |
|
118 | 13 | case $leftQuadrant && $bottomQuadrant: |
|
119 | 8 | return self::Q_BOTTOM_LEFT; |
|
120 | 11 | case $rightQuadrant && $bottomQuadrant: |
|
121 | 7 | return self::Q_BOTTOM_RIGHT; |
|
122 | } |
||
123 | |||
124 | 7 | return -1; |
|
125 | } |
||
126 | |||
127 | /** |
||
128 | * @param Box $box |
||
129 | */ |
||
130 | 7 | public function insert(Box $box) |
|
131 | { |
||
132 | 7 | if ($this->isSplit) { |
|
133 | 5 | $index = $this->getIndex($box); |
|
134 | 5 | if ($index !== -1) { |
|
135 | /** @var QuadTree $node */ |
||
136 | 5 | $node = $this->nodes[$index]; |
|
137 | 5 | $node->insert($box); |
|
138 | 5 | return; |
|
139 | } |
||
140 | } |
||
141 | |||
142 | 7 | $this->objects->add($box); |
|
143 | |||
144 | 7 | if (count($this->objects) > $this->maxObjects && $this->level < $this->maxLevels) { |
|
145 | 6 | if (!$this->isSplit) { |
|
146 | 6 | $this->split(); |
|
147 | } |
||
148 | |||
149 | 6 | foreach ($this->objects as $object) { |
|
150 | 6 | $index = $this->getIndex($object); |
|
151 | 6 | if ($index !== -1) { |
|
152 | 6 | $this->objects->removeElement($object); |
|
153 | /** @var QuadTree $node */ |
||
154 | 6 | $node = $this->nodes[$index]; |
|
155 | 6 | $node->insert($object); |
|
156 | } |
||
157 | } |
||
158 | } |
||
159 | 7 | } |
|
160 | |||
161 | /** |
||
162 | * @return int |
||
163 | */ |
||
164 | 3 | public function count() |
|
165 | { |
||
166 | 3 | $count = $this->objects->count(); |
|
167 | |||
168 | 3 | if ($this->isSplit) { |
|
169 | /** @var QuadTree $node */ |
||
170 | 1 | foreach ($this->nodes as $node) { |
|
171 | 1 | $count += $node->count(); |
|
172 | } |
||
173 | } |
||
174 | |||
175 | 3 | return $count; |
|
176 | } |
||
177 | |||
178 | /** |
||
179 | * @param Box $box |
||
180 | * @return array |
||
181 | */ |
||
182 | 2 | public function retrieve(Box $box) |
|
183 | { |
||
184 | 2 | $return = array(); |
|
185 | |||
186 | 2 | if (!$this->bounds->intersects($box, false)) { |
|
187 | 1 | return array(); |
|
188 | } |
||
189 | |||
190 | /** @var Box $object */ |
||
191 | 2 | foreach ($this->objects as $object) { |
|
192 | 2 | if ($object->intersects($box, false)) { |
|
193 | 2 | $return[] = $object; |
|
194 | } |
||
195 | } |
||
196 | |||
197 | 2 | if ($this->isSplit) { |
|
198 | /** @var QuadTree $node */ |
||
199 | 2 | foreach ($this->nodes as $node) { |
|
200 | 2 | $return = array_merge($return, $node->retrieve($box)); |
|
201 | } |
||
202 | } |
||
203 | |||
204 | 2 | return $return; |
|
205 | } |
||
206 | |||
207 | /** |
||
208 | * @param Box $box |
||
209 | * @return bool |
||
210 | */ |
||
211 | 2 | public function collides(Box $box) |
|
212 | { |
||
213 | 2 | foreach ($this->objects as $object) { |
|
214 | 2 | if ($box->intersects($object)) { |
|
215 | 2 | return true; |
|
216 | } |
||
217 | } |
||
218 | |||
219 | 2 | if ($this->isSplit) { |
|
220 | 1 | $index = $this->getIndex($box); |
|
221 | 1 | $nodes = (-1 === $index) ? $this->nodes : array($this->nodes[$index]); |
|
222 | /** @var QuadTree $node */ |
||
223 | 1 | foreach ($nodes as $node) { |
|
224 | 1 | if ($node->collides($box)) { |
|
225 | 1 | return true; |
|
226 | } |
||
227 | } |
||
228 | } |
||
229 | |||
230 | 2 | return false; |
|
231 | } |
||
232 | |||
233 | /** |
||
234 | * @return string |
||
235 | * @codeCoverageIgnore |
||
236 | */ |
||
237 | public function __toString() |
||
259 | |||
260 | /** |
||
261 | * @return array |
||
262 | */ |
||
263 | 2 | public function getAllObjects() |
|
278 | |||
279 | /** |
||
280 | * @return Box |
||
281 | */ |
||
282 | 3 | public function getBounds() |
|
286 | } |
||
287 |