概要

処理毎にメソッドを分けて、分けたメソッドの中で情報を蓄積していくパターン。

これに名前があったのかと驚いて、コレジャナイと思い 調べ直しをしすぎて調べるのにすごく時間がかかった。

利点

1つの変数に加工した値をまとめて返す場合のリファクタリングがしやすくなったり、可読性が向上する。

HTMLファイルが参照している外部ファイルを、HTMLを埋め込んだ形に変更する。

PHPで書いてみる

1
2
3
4
5
6
7
8
9
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
class HtmlConverter
{
public function toMonolithic(string $html) : string
{
$doc = new DOMDocument();
libxml_use_internal_errors(true);
$doc->loadHTML($html);
libxml_clear_errors();

// linkタグのパース
$linkNode = $doc->getElementsByTagName('link');

// 元のDOMを消すのでforeachでは処理しない
for ($i = 0, $count = $linkNode->length - 1; $i <= $count; $i++) {
$child = $linkNode->item(0);
$path = $child->getAttribute('href');
$css_string = file_get_contents($path);
$replace_elm = $doc->createElement('style', $css_string);
$elm_type_attr = $doc->createAttribute('type');
$elm_type_attr->value = 'text/css';
$replace_elm->appendChild($elm_type_attr);
$child->parentNode->replaceChild($replace_elm, $child);
}

// scriptタグのパース
$scriptNode = $doc->getElementsByTagName('script');

// 元のDOMを消すのでforeachでは処理しない
$skip_item_count = 0;
for ($i = 0, $count = $scriptNode->length - 1; $i <= $count; $i++) {
$child = $scriptNode->item($skip_item_count);
// 外部ファイルを読み込んでいない場合はスキップする
if (empty($child) && ! $child->hasAttribute('src')) {
$skip_item_count++;
continue;
}
$path = $child->getAttribute('src');
$javascript_string = file_get_contents($path);
$replace_elm = $doc->createElement('script');
$replace_elm->appendChild($doc->createTextNode($javascript_string));
$child->parentNode->replaceChild($replace_elm, $child);
}

// imgタグのパース
$imgNode = $doc->getElementsByTagName('img');
foreach ($imgNode as $child) {
$path = $child->getAttribute('src');
$ext = substr($path, (strrpos($path, '.') + 1 ));
$img_binary = base64_encode(file_get_contents($path));
$child->setAttribute('src', 'data:image/'.$ext.';base64,'.$img_binary);
}

return $doc->saveHTML();
}
}

この様に1メソッドが長い。
この中で、情報を蓄積しているDOMDocumentオブジェクト($doc変数)が存在する

以下の様に処理毎にメソッドを分割する。

1
2
3
4
5
6
7
8
9
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
60
61
62
63
64
65
66
67
68
69
70
71
72
class HtmlConverter
{
public function toMonolithic(string $html) : string
{
$doc = new DOMDocument();
libxml_use_internal_errors(true);
$doc->loadHTML($html);
libxml_clear_errors();

$this->replaceLinkTag($doc);
$this->replaceScriptTag($doc);
$this->replaceImgTag($doc);

return $doc->saveHTML();
}

private function replaceLinkTag(DOMDocument $doc) : void
{
// linkタグのパース
$linkNode = $doc->getElementsByTagName('link');

// 元のDOMを消すのでforeachでは処理しない
for ($i = 0, $count = $linkNode->length - 1; $i <= $count; $i++) {
$child = $linkNode->item(0);
$path = $child->getAttribute('href');
$css_string = file_get_contents($path);
$replace_elm = $doc->createElement('style', file_get_contents($path));
$elm_type_attr = $doc->createAttribute('type');
$elm_type_attr->value = 'text/css';

$replace_elm->appendChild($elm_type_attr);
$child->parentNode->replaceChild($replace_elm, $child);
}
}

private function replaceScriptTag(DOMDocument $do) : void
{
// scriptタグのパース
$scriptNode = $doc->getElementsByTagName('script');

// 元のDOMを消すのでforeachでは処理しない
$skip_item_count = 0;
for ($i = 0, $count = $scriptNode->length - 1; $i <= $count; $i++) {
$child = $scriptNode->item($skip_item_count);
// 外部ファイルを読み込んでいない場合はスキップする
if (empty($child) && ! $child->hasAttribute('src')) {
$skip_item_count++;
continue;
}

$path = $child->getAttribute('src');
$javascript_string = file_get_contents($path);
$replace_elm = $doc->createElement('script');

$replace_elm->appendChild($doc->createTextNode($javascript_string));
$child->parentNode->replaceChild($replace_elm, $child);
}
}

private function replaceImgTag(DOMDocument $doc) : void
{
// imgタグのパース
$imgNode = $doc->getElementsByTagName('img');

foreach ($imgNode as $child) {
$path = $child->getAttribute('src');
$ext = substr($path, (strrpos($path, '.') + 1 ));
$img_binary = base64_encode(file_get_contents($path));
$child->setAttribute('src', 'data:image/'.$ext.';base64,'.$img_binary);
}
}
}

なんということでしょう。
長かった1メソッドが、処理毎に分割され、リファクタリングを行いやすい様に生まれ変わりました。

また、これらの分割されたメソッドの引数として、
情報を蓄積しているDOMDocumentオブジェクトを渡してそれぞれのメソッド内で情報を更新させるということを繰り返す。

まとめ

  1. 情報を蓄積するオブジェクトを 作る or 見つける。
  2. 処理毎にメソッドを抽出し、情報を累積するオジェクトをパラメータとして渡す。
  3. これらを繰り返す。

参考

Move Accumulation to Collecting Parameter - Refactoring - Measure and Improve Code Quality continuously with Scrutinizer
書籍 Refactoring to Patterns | Accumulation| Move Accumulation to Collecting Parameter
Move Accumulation to Collecting Parameter