@nqounetです。
前回は、スクレイピング処理には「取得→抽出→保存」という共通の骨格があることを発見しました。今回は、Mooを使ってこの骨格を「基底クラス」として定義していきます。
このシリーズについて
このシリーズは「Mooで覚えるオブジェクト指向プログラミング」シリーズを読了した方を対象に、実践的なWebスクレイパーを作りながらオブジェクト指向設計を深く学ぶシリーズです。
シリーズ全体の目次は以下をご覧ください。
処理の骨格をクラスで表現する
前回整理した「処理の骨格」をおさらいしましょう。
flowchart LR
A[1. 取得<br/>fetch_html] --> B[2. 抽出<br/>extract_data] --> C[3. 保存<br/>save_data]
この流れを、Mooを使ってクラスとして表現していきます。
WebScraper基底クラスを作成する
まず、スクレイパーの「親クラス(基底クラス)」となる WebScraper クラスを作成します。
| |
このクラスには3つの重要なポイントがあります。
ポイント1: 処理の「骨格」を定義するscrapeメソッド
| |
このメソッドは、「取得→抽出→保存」という処理の順番(骨格)を定義しています。この順番は全てのスクレイパーで共通です。
ポイント2: 共通処理を実装する_fetch_htmlメソッド
| |
HTMLの取得処理は全てのスクレイパーで同じなので、基底クラスで実装しています。メソッド名の先頭にアンダースコア(_)をつけているのは、「このメソッドはクラス内部でのみ使用する」という慣習的な表記です。
ポイント3: サブクラスに実装を強制する「抽象メソッド」
| |
これらのメソッドは、基底クラスでは「実装がない」状態です。呼び出すとdieでエラーになります。
これは「抽象メソッド」と呼ばれるパターンで、「このメソッドはサブクラスで必ず実装してね」という意味を持ちます。Perlには他の言語のようなabstractキーワードはありませんが、dieを使うことで同じ効果を実現しています。
なぜこの設計が良いのか
この設計には以下のメリットがあります。
- 処理の順番を保証:
scrapeメソッドで処理の順番が固定されているので、「取得→抽出→保存」の順番が必ず守られる - 共通処理を1箇所に:
_fetch_htmlの実装は基底クラスに1つだけなので、修正時も1箇所だけで済む - 拡張ポイントが明確:
extract_dataとsave_dataをオーバーライドすれば、新しいスクレイパーを簡単に追加できる - コードの意図が明確: クラスの構造を見れば、どこが共通でどこがカスタマイズポイントかがすぐわかる
クラスの構造を図解する
この設計を図にすると、以下のようになります。
classDiagram
class WebScraper {
+url
+scrape()
-_fetch_html()
+extract_data(dom)*
+save_data(data)*
}
note for WebScraper "* = 抽象メソッド<br/>(サブクラスで実装)"
*がついたメソッドは「抽象メソッド」であり、サブクラスでオーバーライドする必要があります。
この基底クラスを使ってみる(失敗例)
まだサブクラスを作っていないので、WebScraperを直接使おうとするとどうなるでしょうか?
| |
実行結果:
| |
extract_dataが実装されていないので、dieでエラーになります。これは意図した動作です。基底クラスはあくまで「骨格」を定義するものであり、具体的な処理はサブクラスで実装する必要があります。
今回のまとめ
今回は以下のことを学びました。
- 処理の「骨格」を基底クラスの
scrapeメソッドで定義する - 共通処理(
_fetch_html)は基底クラスで実装する - サブクラスで実装すべきメソッドは「抽象メソッド」として
dieで宣言する
次回予告
次回は、このWebScraper基底クラスを継承して、NewsScraperとWeatherScraperというサブクラスを作成します。extendsを使って継承し、extract_dataとsave_dataメソッドをオーバーライドして、それぞれのサイトに特化した処理を実装していきます。
「Mooで覚えるオブジェクト指向プログラミング」で学んだ継承とオーバーライドの知識が、ここで活きてきますよ!
お楽しみに!
