プログラミングの抽象化で混乱するケース
プログラミングでは抽象化が重要です。抽象化をすれば汎用的な処理を記述でき、一度の記述で多くの事ができます。
しかし、抽象化しようと思った時にどう抽象化すれば良いか混乱するケースが多いです。
その原因の一つが、抽象化が場合により部分集合と全体集合の全く逆の意味を持つという事があります。どういう事か書いていきます。
部分集合と全体集合
部分集合とは、全体に共通する要素。全体集合とは、全ての要素の集まりです。
そして、抽象化という言葉は対象に着目する場合は全体集合の意味で使われ、性質に使われる場合は部分集合の意味で使われます。
例えば、「人間」という言葉で対象を表現をした場合、人類全体や、人間みんなをイメージさせます。これは人の全体集合です。
一方で、「人間」が性質に言及する文脈で使われた場合、「手足が2本つつあり、二足直立歩行をし、頭がある哺乳類」というように人間全員に共通する性質(要素)に着目します。これが部分集合です。
抽象化とはどちらの視点でも全体の概念を前提にしている点では共通していますが、
全体のなかの部分集合を意味するのか、全体からなる全体集合を意味するのかで意味が真逆になります。
プログラミングで記述を抽象化したいと思った時には、まず自分が部分集合と全体集合のどちらの抽象化をしたいのかを考えてみましょう。
全体集合に対応するプログラミングパターン
全体集合について意識しているという時は、クラスを定義します。
例えば、太郎君、次郎君やマイケルを生成するために人間クラスを定義します。
人間という全体集合から個別の人を生成できるようにします。
この場合、全ての性質を網羅する必要があります。
class Human {
constructor(name, birthPlace) {
this.name = name;
this.birthPlace = birthPlace
}
}
var taro = new Human('taro', 'Japan')
var jiro = new Human('taro', 'Japan')
var michael = new Human('taro', 'British')
全体を定義できるクラスから具体例を生成していきます。
部分集合に対応するプログラミングパターン
部分集合について意識しているという時は、継承するためのクラスを定義します。
例えば、太郎君、次郎君、マイケルは共通してfirstName, lastNameを持つけれど、イギリス人のマイケルだけは加えてmiddleNameを持つという風に考えるとします。
すると以下のようになります。
class Human {
constructor(firstName, lastName) {
this.firstName = firstName;
this.firstName = lastName;
}
}
lass Japanese extends Human {
constructor(firstName, lastName) {
super(firstName, lastName)
}
}
class British extends Human {
constructor(firstName, lastName, middleName) {
super(firstName, lastName, middleName)
}
}
抽象クラスを定義し、具体クラスには抽象クラス + α を定義してあげます。
両者の比較
抽象クラスは定義せずに住むのが一番シンプルです。
抽象クラス定義する場合、具象クラスを大量に定義する事になるからです。
先ほど例でもBritishクラスなどを作るのは冗長すぎるでしょう。
しかしもっと複雑な、例えばhtmlのフォームコントロールをJavascriptから自動生成する抽象化した自作要素を作る場合などはどうなるでしょうか。
##1つめ
<input type="radio" value=3 min=0, max=10 >
##2つめ
<select>
<option value='option1'>Option1</option>
<option value='option1'>Option2</option>
</select>
##3つめ
<input type="radio" name="gender" value="male">
<input type="radio" name="gender" value="female">
<input type="radio" name="gender" value="other">
これらを全てを一般化して表現できるHTML要素を定義できるでしょうか。
また、定義できたとして、その要素は使い勝手が良いでしょうか。
おそらく非常に難しい事になるでしょう。
しかし、radio input要素をJavasSriptから自動生成するのと、select要素をJavasSriptから自動生成する処理を記述するのはそれほど難しくありません。
インターフェースも処理もそれほど複雑ではありません。
createRadioInput(id, value, min, max)
createSelectControl(options)
しかし、フォームの種類を具体的に指定する必要があるため、違う種類のフォームコントロールを動的に生成する処理を記述するのが難しくなります。
では、動的に記述するためのはどうすれば良いでしょうか。
switch case で具象クラスのラッピング
以下のようなメソッドを考えます。
createFormControls(configs) {
return configs.map(config => {
switch(config.type) {
case 'text':
createTextInput(config);
case 'select':
createSelectControl(config);
case 'radio';
createRadioInput(config)
}
})
}
以上のように記述する事で、具体的なインターフェースしか考えてなかったのに、全てのケースに対応できるインターフェースを定義することができます。
動的な処理を記述するには抽象化が複雑すぎると感じた場合、具象クラスへ分割してから、具象クラスへ処理を分岐させるラッパーを作る事を考えましょう。
以上が考え方です。
終わりに
抽象度の高いテーマで最適なうまく説明するのが難しかったです。
少しだけでも伝わる人がいたら幸いです。