※9/16 タイトルを変更しました。
はじめまして。朝日ネット2021年度新卒社員のjiweenです。
オブジェクト指向は今や普通にプログラミングをしていれば必ずと言っていいほど遭遇するありふれた概念ですが、とても一筋縄ではいかない奥の深いプログラミングパラダイムでもあります。オブジェクト指向に悩まされ夜も眠れない全ての人のために、本連載ではなるべく初学者の目線からオブジェクト指向を分析し、オブジェクト指向を実践するための考え方を提案します。6回程続く長い連載になりますがお付き合いいただけると幸いです。
第1回 (今回) では全体の概要とモチベーションについての話をさせていただき、前提知識や仮定の確認を行います。第2回では本連載の結論として、オブジェクト指向設計における重要な考え方を説明します。第3回以降では、結論の具体的な説明と実践的な知識のため、GoFデザインパターンと呼ばれる設計パターンの分析を行います。
- 第1回 はじめに, 概要
- 第2回 結論
- 第3回 Adapter, State, Strategy, Abstract Factory
- 第4回 Template Method, Factory Method, Bridge, Proxy, Composite, Interpreter, Decorator
- 第5回 Visitor, Observer, Iterator, Facade, Mediator
- 第6回 Builder, Singleton, Prototype, Flyweight, Chain of Responsibility, Command, Memento, まとめ
目次(第1回)
概要
GoFデザインパターンは最も伝統的なオブジェクト指向のデザインパターン (=設計のパターン) です。GoFデザインパターンの多くは設計の選択肢として優れていますが、パターンの概念的な解釈やパターン間の使い分けについては一貫性のある説明が存在せず、プログラマ個人の実戦経験に依存しているのが現状です。例えば、よく知られたオブジェクト指向の定義 (カプセル化、継承、ポリモーフィズム) やSOLID原則ではGoFパターン全体をうまく説明できません。パターン全体を一貫した視点で説明することができれば、オブジェクト指向設計を体系的に学ぶために役立つと私は考えています。そこで本連載では、GoFデザインパターンを自然に説明するための主なルールを3つ提案し、それらを使って各パターンを解説していきます。3つのルールのうち2つは書籍「オブジェクト指向のこころ 第2版」が元になっていますが、それだけでは自然に説明しきれない特殊なパターン (Visitorパターン) が存在したため新たに独自の解釈を加えてルールの1つとしました。「オブジェクト指向のこころ」では一部のパターンについてしか分析を行っておらず、パターン間の関連性の分析や分析に使う道具の整理が不十分だったため、本連載ではなるべくそこを補いました。
GoFデザインパターンは歴史が長く様々な応用や解釈が存在するため、 なるべく現代的な事情を加味する ように努めました。GoFデザインパターンの中には、よく使われる自然なパターンの他にも、Visitorパターンのように難解であまり使われないパターンもあります。このようなパターンを深く知ることで、「オブジェクト = データと関連する振る舞いを一体化させたものである」という広く知られた認識は不適切であることが分かりました。
対象読者
本連載は オブジェクト指向言語の基本的な機能を知っている初学者であればなるべく読めるように記述されています。デザインパターンの事前知識は必要ありません。ただしパターンの適用例をあまり扱っていないため、必要に応じてパターンの適用例や実装例を調べながら読んで頂ければと思います。
本連載で具体的なコードを挙げたり言語機能に依存した考察を行ったりする際は、多くの場合Javaを例にとって説明します。
前提知識
以下のキーワードについては説明は行いませんので、事前に理解されているか調べながら読まれることを前提に解説を行います。
- クラス/フィールド (メンバ変数) /メソッド (メンバ関数) /コンストラクタ
- Javaに従い、クラスに属した関数を基本的に メソッド と呼びます
- インターフェイス/インターフェイスの実装
- 継承/オーバーライド
- 多相性 (多態性、ポリモーフィズムとも呼ばれる)
- カプセル化
- 関数オブジェクト/クロージャ
- ジェネリクス
- UMLクラス図の基本的な見方
- UMLクラス図は人によって記法や用語の使い方が微妙に違ったりするので混乱を招きます。ほとんどの場合厳密に分からなくても問題ありません。本連載ではなるべくUML2.5に準拠するように気をつけています。
- 関数型言語
- 個人的な趣味でたまに触れます。詳しくなければ「趣味なんだな」と思って無視して下さい。
- C++, Java, Pythonのようなメジャーなほとんどの言語は手続き型言語です。関数型言語は世界観が大きく異なりますが、知っているとオブジェクト指向設計をより俯瞰的に見ることができます。
動機
私のオブジェクト指向との出会いはC++がきっかけでした。初めに学んだ考え方は「オブジェクトとはデータに関数がくっついたものであり、クラスはオブジェクトの設計図である」というものでした。更にオブジェクト指向の基本要素であるとされるカプセル化、継承、多相性 (ポリモーフィズム) も学びました。実際にアプリケーションを書こうとした時、私はあることに気づきました。機能は分かったがそれを使ってどう設計すればいいのか分からないということです。
そこでデザインパターンを学びました。デザインパターンは先人の経験を元にオブジェクト指向の設計技法をまとめたものです。しかし私には数あるパターンの関係がよく分からず、当時はどれも場当たり的なパターンにしか見えませんでした。現実の複雑な問題に対してデザインパターンをどう使い分け、どう適用すべきかが分からなかったのでとても苦労しました。
今振り返れば私の挫折はある程度仕方のないことでした。初学者にとってオブジェクト指向を体系的に学ぶための情報を得ることは困難でした。2021年 (執筆時) の今でも状況は変わりません。オブジェクト指向設計の入門はなぜ難しいのでしょうか?私は、オブジェクト指向の機能的な側面だけに着目した説明が多く、機能をどう使うのかについての解説が普及していないからだと思っています。これが本連載を書いた一番大きな動機です。
オブジェクト指向と出会ってから5年が経ち、私は最近、当時あまり理解できなかった本の一つ「オブジェクト指向のこころ」を再読する機会がありました。当時抽象的で難しいと思った内容も不思議と読み進めることができ、この本が確かにデザインパターンを導出するためのアイデアを示唆していることに気づきました。続けてデザインパターンの原典である「オブジェクト指向における再利用のためのデザインパターン」を読み全てのGoFデザインパターンを再学習しました。結果として、「オブジェクト指向のこころ」で示唆されている考え方を使えばGoFデザインパターンの多くの部分は合理的に説明できることが分かりました。一方それだけではパターン同士の関係や使い分けを理解するのには不十分だとも感じました。
そこで、私は各パターンの構造を細かく分析し、パターン全体に通じる統一的な解釈が無いか考え始めました。本連載はその成果物です。
余談:
- オブジェクト指向の機能は理解できるのに設計技法が難解で不満を感じる場合、関数型言語を習得してみるのもおすすめです。(命令型)オブジェクト指向言語より関数型言語の方が書くのが難しいと思われていますが、オブジェクト指向の理解に苦労した経験から言って私は必ずしもそうではないと思います。難解に思われている関数型言語特有の機能は概念に対応しており、機能さえ理解できれば分かることは意外に多いと感じます。逆にオブジェクト指向言語の機能に対しては概念的な理解を得るのが困難です。使いこなすという意味ではオブジェクト指向言語はとても難しいのではないでしょうか。
「オブジェクト指向」の定義
オブジェクト指向が何であるかという議論はよく取り沙汰されますが、今回の主題ではありません。何が/どこまでがオブジェクト指向の正統な定義であるべきなのかという問題は、意見の排他性がどうしても必要なときまで後回しにできると考えました。本連載ではオブジェクト指向の一つの解釈とその運用方法を提案することが主目的だと考えたため、オブジェクト指向の正統性についてはあまり注意を払っていません。
既に知識を持っている人の誤解を避けるために書いておくと、本記事におけるオブジェクト指向の解釈は他の解釈と比べて次のような特徴があります。 詳しくない方はお気軽に読み飛ばしていただいて構いません。 オブジェクト指向と言っても色々な立場があるので前置きが必要だという点をご理解ください。
- 静的型付け、命令型、かつクラスベースなオブジェクト指向を前提とします1 。オブジェクトの持つデータと振る舞いは静的に設計され、オブジェクトへの通信は静的に契約・検査されます。
- 動的型付けなオブジェクト指向は使う道具が静的型付けと異なるので注意してください。
- 「カプセル化・継承・多相性(ポリモーフィズム)」からオブジェクト指向を定義する立場に近いですが、カプセル化を特に重要視し拡大解釈しています。
- 静的型付けにおける「継承」には実装の継承と振る舞い宣言の継承がありますが、前者は重要ではないと考えています。むしろ実装の継承 (≒クラスの継承) は乱用に気をつけるべきです。振る舞い宣言の継承 (≒インターフェイスの実装) は、静的型付けと多相性 (ポリモーフィズム) の両立のため重要です。
- 慣例的には「継承」と言うと「クラスの継承」を指すことが多いですが、本連載では型の特性を受け継ぐという抽象的な意味で「継承」を使い、クラスの継承だけを指す時は明確に「クラス継承」と呼びます。
- オブジェクトはデータと振る舞いをいくつか集めたものです。ただしデータだけ、または振る舞いだけのオブジェクトも自然に発生します。あるデータと振る舞いが単に関係しているからという理由でオブジェクトとして一体化する必然性はありませんし、すべきでない場合もあります。(詳細は後述します)
オブジェクト指向における「デザインパターン」
デザインパターンとは先人の経験に基づいたデザイン (設計) のパターンです。実装のパターンではないことに注意してください。つまりプログラムによって問題をどう解くかということよりも、問題をどう捉えるか、どう考えるかというレイヤーに関するパターンです。高次のレイヤーをパターン化することで、設計の思考材料として使えるだけでなく プログラマ間の共通言語としても機能する ということが言われています。
GoFデザインパターンとは
オブジェクト指向におけるデザインパターンの先駆けとなった、有名な23個のパターンのことです。単にデザインパターンと言った場合これらのパターンを指すこともあります。「オブジェクト指向における再利用のためのデザインパターン」(←邦題) という本で提唱されました。この本は著者が4人いたのでGoF = Gang of Four (4人のやつら) と名付けられました。
23個のパターンは次のような名前が付いています。(本連載で紹介する順)
- Adapterパターン
- Stateパターン
- Strategyパターン
- Abstract Factoryパターン
- Template Methodパターン
- Factory Methodパターン
- Bridgeパターン
- Proxyパターン
- Compositeパターン
- Interpreterパターン
- Decoratorパターン
- Visitorパターン
- Observerパターン
- Iteratorパターン
- Facadeパターン
- Mediatorパターン
- Builderパターン
- Singletonパターン
- Prototypeパターン
- Flyweightパターン
- Chain of Responsibilityパターン
- Commandパターン
- Mementoパターン
なぜ私が今必死に23項目もタイピングしたかというと名前が面白いからです。名前だけで言えばMemento (=形見) やAbstract Factory (=抽象工場) はかなりかっこいいので好きです。Bridge (=橋) , Visitor (=訪問者) , Decorator(=装飾者) なども詩のタイトルのような趣があります。
GoFデザインパターンは古いのか
GoFデザインパターンは普遍的なパターンも多く含んでいますが、中には 当時の言語環境上の都合に依存したパターン もあります。そのようなパターンは、現代では意義が薄らいでいたり別の指針によって置換されるべきであったりすることがあります。また、重要なパターンは言語標準やフレームワークに実装されているので自分でパターンを適用する必要はない、という意見もあります。確かにパターンとしてはあまり役に立たなくなったものもあると感じます。GoFデザインパターンの提唱者ら自身も「GoFデザインパターンを完璧で不変なものとは思っていない」と述べており、GoFデザインパターンの全てが時代を超えて役に立ち続ける道理はありません。
しかし、オブジェクト指向の基本的なパーツはGoFデザインパターンが提唱されてから現代に至るまでほとんど変わりませんでした。強力で合理的な型システムの普及、関数型プログラミングスタイルの部分的な輸入、DI (依存性の注入) ツール、メモリ&ライフサイクル管理理論の発達、クラス継承廃止運動、null廃止運動などオブジェクト指向に関わる言語の進化は続いていますが、いずれの変化もオブジェクト指向の考え方に相反したり思想レベルで代替するようなものではありません。更に広い目で見れば、システムが複雑化した現代ではプログラマへの属人化や一つのツールの多機能化が見直されつつあり、オブジェクト指向などに基づいた最小限の理論でクリーンなコードを書くことは以前より重要になったと思います。オブジェクト指向の考え方自体は汎用的なもので、言語開発者のような理論寄りのプログラマだけに役立つものではありません。
そしてGoFデザインパターンは、オブジェクト指向の考え方を理解する足がかりとして有用です。GoFデザインパターンにはパターンとしての魅力も (恐るべきことに未だに) 残っているのですが、 言葉だけでは説明しづらいオブジェクト指向の重要な性質を示唆してくれている という魅力があります。
分析を通して感じた感想を述べると、 GoFデザインパターンのうち9個のパターンはオブジェクト指向の重要な考え方を示唆 しており、オブジェクト指向設計を理解するための良い教材となりました。第3回以降ではこれらのパターンと、その類型を優先的に解説していきます。
- Adapterパターン
- Stateパターン
- Bridgeパターン
- Compositeパターン
- Visotorパターン
- Observerパターン
- Iteratorパターン
- Facadeパターン
- Mediatorパターン
このうちVisitorパターンとBridgeパターンではパターンを分析することで初めて気づいた考え方がありました。
その他のパターンについても、大部分は重要なパターンの良い応用例や類型となっています。今回の調査と分析によって、 Singletonパターンのみ現代的知識によって明確に否定されるべきパターンである と判断しました。(Singletonパターンの説明は発展的な内容を含むので連載の終盤で紹介します)
GoFデザインパターンがオブジェクト指向の理解に役立つというのは以上の体感に基づいています。オブジェクト指向設計の考え方が凝縮されたGoFデザインパターンを読み解くことで、オブジェクト指向の気持ちが分かるようになり、設計の判断で悩むことが減り、夜も眠れるようになります。
次回予告
第一回は以上になります。今回は主に本連載の概要とモチベーション、GoFデザインパターンの概要について触れました。今回の記事で分からない用語などが多かった人は興味があればぜひ調べてみてください。第2回では本連載の結論である私なりのオブジェクト指向の解釈を説明します。お楽しみに!
採用情報
朝日ネットでは新卒採用・キャリア採用を行っております。
- C++, Java, C#, Swiftなどで強くサポートされているような。↩