Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like EnumSet 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 EnumSet, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
16 | class EnumSet implements Iterator, Countable |
||
17 | { |
||
18 | /** |
||
19 | * The classname of the Enumeration |
||
20 | * @var string |
||
21 | */ |
||
22 | private $enumeration; |
||
23 | |||
24 | /** |
||
25 | * Ordinal number of current iterator position |
||
26 | * @var int |
||
27 | */ |
||
28 | private $ordinal = 0; |
||
29 | |||
30 | /** |
||
31 | * Highest possible ordinal number |
||
32 | * @var int |
||
33 | */ |
||
34 | private $ordinalMax; |
||
35 | |||
36 | /** |
||
37 | * Integer or binary (little endian) bitset |
||
38 | * @var int|string |
||
39 | */ |
||
40 | private $bitset = 0; |
||
41 | |||
42 | /**#@+ |
||
43 | * Defined the private method names to be called depended of |
||
44 | * how the bitset type was set too. |
||
45 | * ... Integer or binary bitset. |
||
46 | * ... *Int or *Bin method |
||
47 | * |
||
48 | * @var string |
||
49 | */ |
||
50 | private $fnDoRewind = 'doRewindInt'; |
||
51 | private $fnDoCount = 'doCountInt'; |
||
52 | private $fnDoGetOrdinals = 'doGetOrdinalsInt'; |
||
53 | private $fnDoGetBit = 'doGetBitInt'; |
||
54 | private $fnDoSetBit = 'doSetBitInt'; |
||
55 | private $fnDoUnsetBit = 'doUnsetBitInt'; |
||
56 | private $fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeInt'; |
||
57 | private $fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeInt'; |
||
58 | /**#@-*/ |
||
59 | |||
60 | /** |
||
61 | * Constructor |
||
62 | * |
||
63 | * @param string $enumeration The classname of the enumeration |
||
64 | * @throws InvalidArgumentException |
||
65 | */ |
||
66 | 130 | public function __construct($enumeration) |
|
97 | |||
98 | /** |
||
99 | * Get the classname of the enumeration |
||
100 | * @return string |
||
101 | */ |
||
102 | 2 | public function getEnumeration() |
|
106 | |||
107 | /** |
||
108 | * Attach a new enumerator or overwrite an existing one |
||
109 | * @param Enum|null|boolean|int|float|string $enumerator |
||
110 | * @return void |
||
111 | * @throws InvalidArgumentException On an invalid given enumerator |
||
112 | */ |
||
113 | 82 | public function attach($enumerator) |
|
118 | |||
119 | /** |
||
120 | * Detach the given enumerator |
||
121 | * @param Enum|null|boolean|int|float|string $enumerator |
||
122 | * @return void |
||
123 | * @throws InvalidArgumentException On an invalid given enumerator |
||
124 | */ |
||
125 | 14 | public function detach($enumerator) |
|
130 | |||
131 | /** |
||
132 | * Test if the given enumerator was attached |
||
133 | * @param Enum|null|boolean|int|float|string $enumerator |
||
134 | * @return boolean |
||
135 | */ |
||
136 | 16 | public function contains($enumerator) |
|
141 | |||
142 | /* Iterator */ |
||
143 | |||
144 | /** |
||
145 | * Get the current enumerator |
||
146 | * @return Enum|null Returns the current enumerator or NULL on an invalid iterator position |
||
147 | */ |
||
148 | 24 | public function current() |
|
157 | |||
158 | /** |
||
159 | * Get the ordinal number of the current iterator position |
||
160 | * @return int |
||
161 | */ |
||
162 | 14 | public function key() |
|
166 | |||
167 | /** |
||
168 | * Go to the next valid iterator position. |
||
169 | * If no valid iterator position is found the iterator position will be the last possible + 1. |
||
170 | * @return void |
||
171 | */ |
||
172 | 38 | public function next() |
|
181 | |||
182 | /** |
||
183 | * Go to the first valid iterator position. |
||
184 | * If no valid iterator position was found the iterator position will be 0. |
||
185 | * @return void |
||
186 | * @see doRewindBin |
||
187 | * @see doRewindInt |
||
188 | */ |
||
189 | 24 | public function rewind() |
|
193 | |||
194 | /** |
||
195 | * Go to the first valid iterator position. |
||
196 | * If no valid iterator position was found the iterator position will be 0. |
||
197 | * |
||
198 | * This is the binary bitset implementation. |
||
199 | * |
||
200 | * @return void |
||
201 | * @see rewind |
||
202 | * @see doRewindInt |
||
203 | */ |
||
204 | 10 | private function doRewindBin() |
|
205 | { |
||
206 | 10 | if (trim($this->bitset, "\0") !== '') { |
|
207 | 10 | $this->ordinal = -1; |
|
208 | 10 | $this->next(); |
|
209 | 5 | } else { |
|
210 | 2 | $this->ordinal = 0; |
|
211 | } |
||
212 | 10 | } |
|
213 | |||
214 | /** |
||
215 | * Go to the first valid iterator position. |
||
216 | * If no valid iterator position was found the iterator position will be 0. |
||
217 | * |
||
218 | * This is the binary bitset implementation. |
||
219 | * |
||
220 | * @return void |
||
221 | * @see rewind |
||
222 | * @see doRewindBin |
||
223 | */ |
||
224 | 14 | private function doRewindInt() |
|
233 | |||
234 | /** |
||
235 | * Test if the iterator in a valid state |
||
236 | * @return boolean |
||
237 | */ |
||
238 | 26 | public function valid() |
|
242 | |||
243 | /* Countable */ |
||
244 | |||
245 | /** |
||
246 | * Count the number of elements |
||
247 | * |
||
248 | * @return int |
||
249 | * @see doCountBin |
||
250 | * @see doCountInt |
||
251 | */ |
||
252 | 26 | public function count() |
|
256 | |||
257 | /** |
||
258 | * Count the number of elements. |
||
259 | * |
||
260 | * This is the binary bitset implementation. |
||
261 | * |
||
262 | * @return int |
||
263 | * @see count |
||
264 | * @see doCountInt |
||
265 | */ |
||
266 | 10 | private function doCountBin() |
|
285 | |||
286 | /** |
||
287 | * Count the number of elements. |
||
288 | * |
||
289 | * This is the integer bitset implementation. |
||
290 | * |
||
291 | * @return int |
||
292 | * @see count |
||
293 | * @see doCountBin |
||
294 | */ |
||
295 | 16 | private function doCountInt() |
|
329 | |||
330 | /** |
||
331 | * Check if this EnumSet is the same as other |
||
332 | * @param EnumSet $other |
||
333 | * @return bool |
||
334 | */ |
||
335 | 6 | public function isEqual(EnumSet $other) |
|
340 | |||
341 | /** |
||
342 | * Check if this EnumSet is a subset of other |
||
343 | * @param EnumSet $other |
||
344 | * @return bool |
||
345 | */ |
||
346 | 8 | View Code Duplication | public function isSubset(EnumSet $other) |
354 | |||
355 | /** |
||
356 | * Check if this EnumSet is a superset of other |
||
357 | * @param EnumSet $other |
||
358 | * @return bool |
||
359 | */ |
||
360 | 8 | View Code Duplication | public function isSuperset(EnumSet $other) |
368 | |||
369 | /** |
||
370 | * Produce a new set with enumerators from both this and other (this | other) |
||
371 | * |
||
372 | * FIXME: No variadic params with type constraints because of https://github.com/facebook/hhvm/issues/6954 |
||
373 | * |
||
374 | * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union |
||
375 | * @return EnumSet |
||
376 | */ |
||
377 | 4 | View Code Duplication | public function union(...$others) |
396 | |||
397 | /** |
||
398 | * Produce a new set with enumerators common to both this and other (this & other) |
||
399 | * |
||
400 | * FIXME: No variadic params with type constraints because of https://github.com/facebook/hhvm/issues/6954 |
||
401 | * |
||
402 | * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union |
||
403 | * @return EnumSet |
||
404 | */ |
||
405 | 4 | View Code Duplication | public function intersect(...$others) |
424 | |||
425 | /** |
||
426 | * Produce a new set with enumerators in this but not in other (this - other) |
||
427 | * |
||
428 | * FIXME: No variadic params with type constraints because of https://github.com/facebook/hhvm/issues/6954 |
||
429 | * |
||
430 | * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union |
||
431 | * @return EnumSet |
||
432 | */ |
||
433 | 4 | View Code Duplication | public function diff(...$others) |
456 | |||
457 | /** |
||
458 | * Produce a new set with enumerators in either this and other but not in both (this ^ (other | other)) |
||
459 | * |
||
460 | * FIXME: No variadic params with type constraints because of https://github.com/facebook/hhvm/issues/6954 |
||
461 | * |
||
462 | * @param EnumSet ...$others Other EnumSet(s) of the same enumeration to produce the union |
||
463 | * @return EnumSet |
||
464 | */ |
||
465 | 4 | View Code Duplication | public function symDiff(...$others) |
488 | |||
489 | /** |
||
490 | * Get ordinal numbers of the defined enumerators as array |
||
491 | * @return int[] |
||
492 | */ |
||
493 | 28 | public function getOrdinals() |
|
497 | |||
498 | /** |
||
499 | * Get ordinal numbers of the defined enumerators as array. |
||
500 | * |
||
501 | * This is the binary bitset implementation. |
||
502 | * |
||
503 | * @return int[] |
||
504 | * @see getOrdinals |
||
505 | * @see goGetOrdinalsInt |
||
506 | */ |
||
507 | 4 | private function doGetOrdinalsBin() |
|
526 | |||
527 | /** |
||
528 | * Get ordinal numbers of the defined enumerators as array. |
||
529 | * |
||
530 | * This is the integer bitset implementation. |
||
531 | * |
||
532 | * @return int[] |
||
533 | * @see getOrdinals |
||
534 | * @see doGetOrdinalsBin |
||
535 | */ |
||
536 | 24 | private function doGetOrdinalsInt() |
|
546 | |||
547 | /** |
||
548 | * Get values of the defined enumerators as array |
||
549 | * @return null[]|bool[]|int[]|float[]|string[] |
||
550 | */ |
||
551 | 12 | public function getValues() |
|
560 | |||
561 | /** |
||
562 | * Get names of the defined enumerators as array |
||
563 | * @return string[] |
||
564 | */ |
||
565 | 4 | public function getNames() |
|
574 | |||
575 | /** |
||
576 | * Get the defined enumerators as array |
||
577 | * @return Enum[] |
||
578 | */ |
||
579 | 4 | public function getEnumerators() |
|
588 | |||
589 | /** |
||
590 | * Get binary bitset in little-endian order |
||
591 | * |
||
592 | * @return string |
||
593 | */ |
||
594 | 12 | public function getBinaryBitsetLe() |
|
598 | |||
599 | /** |
||
600 | * Get binary bitset in little-endian order. |
||
601 | * |
||
602 | * This is the binary bitset implementation. |
||
603 | * |
||
604 | * @return string |
||
605 | */ |
||
606 | 8 | private function doGetBinaryBitsetLeBin() |
|
610 | |||
611 | /** |
||
612 | * Get binary bitset in little-endian order. |
||
613 | * |
||
614 | * This is the integer bitset implementation. |
||
615 | * |
||
616 | * @return string |
||
617 | */ |
||
618 | 4 | private function doGetBinaryBitsetLeInt() |
|
623 | |||
624 | /** |
||
625 | * Set binary bitset in little-endian order |
||
626 | * |
||
627 | * NOTE: It resets the current position of the iterator |
||
628 | * |
||
629 | * @param string $bitset |
||
630 | * @return void |
||
631 | * @throws InvalidArgumentException On a non string is given as Parameter |
||
632 | */ |
||
633 | 26 | public function setBinaryBitsetLe($bitset) |
|
644 | |||
645 | /** |
||
646 | * Set binary bitset in little-endian order |
||
647 | * |
||
648 | * NOTE: It resets the current position of the iterator |
||
649 | * |
||
650 | * @param string $bitset |
||
651 | * @return void |
||
652 | * @throws InvalidArgumentException On a non string is given as Parameter |
||
653 | */ |
||
654 | 14 | private function doSetBinaryBitsetLeBin($bitset) |
|
683 | |||
684 | /** |
||
685 | * Set binary bitset in little-endian order |
||
686 | * |
||
687 | * NOTE: It resets the current position of the iterator |
||
688 | * |
||
689 | * @param string $bitset |
||
690 | * @return void |
||
691 | * @throws InvalidArgumentException On a non string is given as Parameter |
||
692 | */ |
||
693 | 10 | private function doSetBinaryBitsetLeInt($bitset) |
|
713 | |||
714 | /** |
||
715 | * Get binary bitset in big-endian order |
||
716 | * |
||
717 | * @return string |
||
718 | */ |
||
719 | 2 | public function getBinaryBitsetBe() |
|
723 | |||
724 | /** |
||
725 | * Set binary bitset in big-endian order |
||
726 | * |
||
727 | * NOTE: It resets the current position of the iterator |
||
728 | * |
||
729 | * @param string $bitset |
||
730 | * @return void |
||
731 | * @throws InvalidArgumentException On a non string is given as Parameter |
||
732 | */ |
||
733 | 4 | public function setBinaryBitsetBe($bitset) |
|
740 | |||
741 | /** |
||
742 | * Get a bit at the given ordinal number |
||
743 | * |
||
744 | * @param int $ordinal Ordinal number of bit to get |
||
745 | * @return boolean |
||
746 | */ |
||
747 | 6 | public function getBit($ordinal) |
|
755 | |||
756 | /** |
||
757 | * Get a bit at the given ordinal number. |
||
758 | * |
||
759 | * This is the binary bitset implementation. |
||
760 | * |
||
761 | * @param int $ordinal Ordinal number of bit to get |
||
762 | * @return boolean |
||
763 | * @see getBit |
||
764 | * @see doGetBitInt |
||
765 | */ |
||
766 | 18 | private function doGetBitBin($ordinal) |
|
770 | |||
771 | /** |
||
772 | * Get a bit at the given ordinal number. |
||
773 | * |
||
774 | * This is the integer bitset implementation. |
||
775 | * |
||
776 | * @param int $ordinal Ordinal number of bit to get |
||
777 | * @return boolean |
||
778 | * @see getBit |
||
779 | * @see doGetBitBin |
||
780 | */ |
||
781 | 32 | private function doGetBitInt($ordinal) |
|
785 | |||
786 | /** |
||
787 | * Set a bit at the given ordinal number |
||
788 | * |
||
789 | * @param int $ordinal Ordnal number of bit to set |
||
790 | * @param bool $bit The bit to set |
||
791 | * @return void |
||
792 | * @see doSetBitBin |
||
793 | * @see doSetBitInt |
||
794 | * @see doUnsetBin |
||
795 | * @see doUnsetInt |
||
796 | */ |
||
797 | 4 | public function setBit($ordinal, $bit) |
|
809 | |||
810 | /** |
||
811 | * Set a bit at the given ordinal number. |
||
812 | * |
||
813 | * This is the binary bitset implementation. |
||
814 | * |
||
815 | * @param int $ordinal Ordnal number of bit to set |
||
816 | * @return void |
||
817 | * @see setBit |
||
818 | * @see doSetBitInt |
||
819 | */ |
||
820 | 14 | private function doSetBitBin($ordinal) |
|
825 | |||
826 | /** |
||
827 | * Set a bit at the given ordinal number. |
||
828 | * |
||
829 | * This is the binary bitset implementation. |
||
830 | * |
||
831 | * @param int $ordinal Ordnal number of bit to set |
||
832 | * @return void |
||
833 | * @see setBit |
||
834 | * @see doSetBitBin |
||
835 | */ |
||
836 | 70 | private function doSetBitInt($ordinal) |
|
840 | |||
841 | /** |
||
842 | * Unset a bit at the given ordinal number. |
||
843 | * |
||
844 | * This is the binary bitset implementation. |
||
845 | * |
||
846 | * @param int $ordinal Ordinal number of bit to unset |
||
847 | * @return void |
||
848 | * @see setBit |
||
849 | * @see doUnsetBitInt |
||
850 | */ |
||
851 | 6 | private function doUnsetBitBin($ordinal) |
|
856 | |||
857 | /** |
||
858 | * Unset a bit at the given ordinal number. |
||
859 | * |
||
860 | * This is the integer bitset implementation. |
||
861 | * |
||
862 | * @param int $ordinal Ordinal number of bit to unset |
||
863 | * @return void |
||
864 | * @see setBit |
||
865 | * @see doUnsetBitBin |
||
866 | */ |
||
867 | 10 | private function doUnsetBitInt($ordinal) |
|
871 | } |
||
872 |
Let’s take a look at an example: