【オブジェクト指向の考え方】オブジェクト指向は滅びたのか、定着したのか

プログラミング共通

オブジェクト指向を説明します。

オブジェクト指向の本質は、2つの似たクラスを抽象化してスーパークラスを作ることではありません。とにかくクラスを作るというのが本質です。

Jeff Bayの9つの規則を説明し、例でオブジェクト指向の設計、プログラミングをどうやっていくかを実践します。

オブジェクトは滅びたのか、定着したのか

そう遠くない昔、2010年代、本屋にはオブジェクト指向の本がかなりたくさんありました。

2022年現在、その多くは本屋から消え、中古でしか手に入りません。オブジェクト指向の本の需要がなくなっているのは確かなようです。

オブジェクト指向は滅びたのでしょうか?それとも、本にしなくてもよいほど当たり前のこととして定着したのでしょうか?

プログラミング言語の説明の途中でオブジェクト指向が出てくるので、オブジェクト指向だけを説明する本は不要となったのでしょうか。

以下の本の第5章Jeff Bay氏の記事を元にオブジェクト指向の説明を試みたいと思います。

オブジェクト指向とは?

クラスはとにかく作るもの

オブジェクト指向の入門記事では、「乗り物と車、自転車」とか「動物と犬、猫」の例が出てきます。

実生活に当てはめたクラスと継承の説明、一見分かりやすそうで分かりません。実際の業務でこのような考えでクラスを作り継承することはほとんどないからです。

オブジェクト指向では、同じような2つの事象が出てきたから作るようなものではなくクラスはとにかく作るものです。再利用とかはほぼ考えていません。とにかくクラスを作りましょう。

C言語から来ると、データもないのにクラスを作るという行為に違和感を感じると思いますが、オブジェクト指向では、処理があったらクラスを作ります。クラスをとにかく作ってクラスだらけにしたとき、オブジェクト指向となります。

本記事の目標は、クラスはとにかく作るものだ、ということを理解することにあります。

Jeff Bayの9つの規則

オブジェクト指向の本質は同じような2つの事象を抽象化することではありません。

Jeff Bayによれば、「小さい大量のオブジェクトがお互いメッセージを送りながら協調し複雑なシステムを構築する」となるのがオブジェクト指向の理想です。

Jeff Bayは9つの規則によってこの理想を実現すると宣言しました。

  1. ひとつのメソッドにインデントは1つまでにすること
  2. else句を使用しないこと
  3. すべてのprimitive型、文字列型をラップすること
  4. 1行につきドットは1つまで
  5. 名前を省略しないこと
  6. すべてのエンティティを小さくすること
  7. ひとつのクラスにインスタンス変数は2つまで
  8. ファーストクラスコレクションを使用する
  9. getter/setterを使用しないこと

本記事では、この9つの規則を軽く説明して、例として簡単なプログラムをとりあげます。

きっと「オブジェクト指向では、クラスはとにかく作るものだ」を理解してもらえます。

※実際の現場でこの規則を守るのは相当厳しいです。努力目標くらいに捉えてもよいかもしれません。

9つの規則の説明の前に

1つのクラスで、20個のメソッドと30個のメンバ変数を持ち、10K行を超えるステップ数。

こういう所謂「神クラス」を作ってはいけません。1つのメソッドで数百行もあるのはそれだけで悪です。オブジェクト指向とか関係なく抹殺すべきです。

初めてオブジェクト指向をやる人、C言語からオブジェクト指向にやってきた人、これだけは覚えておきましょう。

巨大なクラス、巨大なメソッドはそれだけで悪です。小さい大量のオブジェクトが協調するという状態の対極にあります。

逆に言うと、巨大なクラスを駆逐するのがJeff Bayの9つの規則です。

#1 ひとつのメソッドにインデントは1つまでにすること

深いインデントを作ってはいけません。インデントが3段とかになると頭が痛くなります。

void method(){
 if(AAA){
  if(BBB){
   for(;;){
     .....
   }
  }
 }
}

こういうコードは真っ先にリファクタリングしてしまいます。

一番内側のfor文をメソッド化します。

void method(){
 if(AAA){
  if(BBB){
   method1();
  }
 }
}

voide method1(){
 for(;;){
  .....
 }
}

さらにif(BBB)もメソッド化します。

void method(){
 if(AAA){
  method2();
 }
}

void method2(){
  if(BBB){
   method1();
  }
}

void method1(){
 for(;;){
  .....
 }
}

一つの制御文で一つのメソッドということを意識して、メソッドの大きさを小さくしていきます。

人によっては「最初のif if forと重なっている形の方が分かりやすい」と思う人がいるかもしれません。

しかし、その考えは捨ててください。とにかくメソッドを小さくしていくことが正義です。

#2 else句を使用しないこと

else句やswitch文は使わないようにしましょう。

swith (someType){
 case A:
   doA();
 case B:
   doB();
 case C;
   doC();
}

みたいなコードが頭に浮かんだ時、以下のようなコードにできないかを検討しましょう。

class someClass(){
 virtual void do()=0;
}

someObj.do();

自力で考えるよりも先人の知恵に頼りましょう。デザインパターンが手を貸してくれます。Strategy、Stateが常套手段です。

デザインパターンなんか知らない、という人はとりあえず1メソッド1制御文という原則にして、switch文だけのメソッドを作っておきましょう。

#3 すべてのprimitive型、文字列型をラップすること

プログラミング言語が提供している型はラップしましょう。

intやstringをそのまま使うのではなくラップすると特徴が際立ってきます。

クラスはとにかく作るものなのです。

class Point {
 int x;
 int y;
 string pointName;
}

というメンバがあるときは、intやstringを極力意味がある言葉にしてしまいます。

class Point {
 Coordinate c;
 PointName n;
}

class Coordinate {
 int x;
 int y;
}

class PointName {
 string n;
}

メンバ名に意味があるよりクラス名に意味があるように書き換えることを心がけましょう。

こういう分け方をしていくとメソッドの実装がどこに所属すべきかについて考えることになります。

上記でx,yの座標に関することであればCoordinateクラスで実装すべきだし、点の名前に関することであればPointNameクラスで実装すべきです。

クラスはとにかく作るものというのが段々理解できてくると思います。

#4 1行につきドットは1つまで

隣の人には話しかけていいですが、隣の隣の人と喋ってはいけません。

クラスの責任を明確にし独立性を保ちます。

Neighbor.yourNeighbor.talk();

ではなく

Neighbor.talk();

としましょう。

#5 名前を省略しないこと

長い名前をつけろ、という意味ではありません。

省略しなければならないほど長い名前をつけるときは考えなければなりません。

  • ひとつのクラスに複数の機能を与えていませんか?
  • ひとつのメソッドに複数の処理をやらせていませんか?

複数の処理をやるメソッドを作ると名前をつけるのが困難になります。

名前を付けるのに迷ったら振り返るべきです。

複数処理を1メソッドにやらせていたら、複数のメソッドに分けていきましょう。新たにクラスを作る必要があるかもしれません。

#6 すべてのエンティティを小さくすること

#7 ひとつのクラスにインスタンス変数は2つまで

#6, #7は実際に守るのは困難です(少なくとも筆者には)。信念を理解しましょう。

  • クラスは50行以内
  • 1クラス1変数が原則で最大2つまで
  • 1メソッド20行以内

なるべく小さいクラス、小さいメソッドを作って、大きくなりそうになったら分割する、ということを心がけましょう。

#8 ファーストクラスコレクションを使用する

#3のリスト版です。コレクション機能を持つvectorやlistは機能が多すぎるので必ずラップしましょう。

class Game {
 std::vector<Actor> actors;
 std::vector<Items> items;
}

というクラスがあったら、vectorの部分をラップします。

class ActorManager {
 std::vector<Actor> actors;
}

class ItemManager {
 std::vector<Item> items;
}

こうすることによって、凝集度が高まっていきます。とにかくクラスを作ります

#9 getter/setterを使用しないこと

メンバ変数を作ったら真っ先にgetter/setterを作る人がいますが、決して作らないようにしましょう。

「求めるな、命ぜよ」

というのを理解しましょう。

「この値は何?」と訊くのではなく「やれ」と命じるように作ります。

classA A = obj1.getA();
classB B = obj1.getB();

do(A, B);

というコードよりも以下の方がセンスがあることを理解しましょう。

obj1.do();

これこそがオブジェクト指向そのものです。

簡単な例 (蛇足だろうか?)

この9つの規則を読んだ時、筆者は感動に打ち震えました(ちょっと大げさ)。

今までやっていたオブジェクト指向は偽物だった、これからは正しくオブジェクト指向のプログラムを書き、質の良いエンジニアになろうと心に決めました。

蛇足的ですが、オブジェクト指向の例を書いてみたいと思います。

クラスはとにかく作るものです。

例えば以下のような簡単な機能を考えます。

  • 何らかのデータをinputとして関数コールされる
  • 中で何かの加工をする
  • 外の関数をコールする

単にこれだけの機能とします。

あまりに単純すぎるので、単なる関数でいいんじゃないかと思うかもしれませんが、ここはあえてクラスを使っていきます。

まず、何も考えずにインタフェースのところにクラスを置きます。ついでに加工も処理がありますので、クラスを配置します。

これが最も小さいクラス図です。どういう処理をするかによってどんどん拡張していきます。

例えば、Receiverのところは

  • インタフェースと実装を分けた方がいいかな
  • ついでに仮想クラスも作っとくか
  • 引数のデータは構造体みたいにしとこうか

みたいに考えたとすると、以下のようなクラス図になります。

Calculatorが何の処理をするかによらず、上記のような構造は作っておきたいです。

筆者は、上記のようにpImplぽくするのが一番多く、他にはデザインパターンFacade, Command, Mediatorなどをやったことがあります。

Calculatorのところが本処理なので、9つのルールに従ってどんどんクラスをどんどん作ってください。

9つのルールになるべく従いながら、クラスをとにかく作っていってください。

「小さい大量のオブジェクトがお互いメッセージを送りながら協調」という目標は自然と達成されると思います。

まとめ

オブジェクト指向の考え方を説明しました。

ぜひオブジェクト指向を身に付けてください。

本記事の元となった本は以下です。

また、デザインパターンは必須に近いです。どこかの段階で勉強しておいて損はありません。

>>> 【ITエンジニア】工程ごとの分類

コメント

スポンサーリンク
スポンサーリンク
スポンサーリンク
スポンサーリンク
スポンサーリンク
タイトルとURLをコピーしました