Share
Embed
オブジェクト指向は必要なのか / Is object-oriented needed?
2024/3/24に開催されたObject-Oriented Conferenceでの登壇資料です。
https://ooc.dev/2024/
Naoki Kishida
March 24, 2024
Transcript
35 slides
-
2024/03/24 2 自己紹介 • きしだ なおき • LINEヤフー • X(twitter):
@kis • blog: きしだのHatena • (nowokay.hatenablog.com) • 「プロになるJava」というJavaの本を書いてます • WEB+DB PRESS vol.132でオブジェクト指向特集書いてます -
モチベーション • プログラム初心者が「まだオブジェクト指向がわからないので」 のような発言をする • forループが書けるようになるのが先では? • 「オブジェクト指向」がわかっても、やりたいと思ってること できるようにならんで •
「オブジェクト指向」が初心者の到達するべき目標になっている • 「オブジェクト指向」がわかればプログラムが組める? • プログラムが組めないのは「オブジェクト指向」がわかってない? • こういう不毛な誤解はなくなるべき • 初心者に「オブジェクト指向」は不要。かなりあとまわしでいい。 -
モチベーション • 「オブジェクト指向」はあいまいな意味付けのまま放置されてる • 現場の技術コミュニケーションで使わないので共通定義が求められて いない • 「オブジェクト指向」以外の言葉で範囲を狭めて誤解なく表現で きるものについては、その言葉を使うほうがいいのでは? •
開発作業において「オブジェクト指向」が、「オブジェクト指 向」だけが意味できるものを拾いだして、なぜ使わないのか、ど う使うのか明確にしたほうがいいのでは? -
簡単な歴史 • 1960年代: Simulaによる原初の実装 • 1970年代: アラン・ケイによる命名とコンセプト化 • 1980年代: C++で一般化
• カプセル化・継承・ポリモーフィズムという実用的な定義 • 1990年代: GUIの普及で大流行 • オブジェクト指向と付ければ売れるので、何でもオブジェクト指向化 • 2000年代: Web技術の発展により斜陽化 • 関数型の流行、手法からプロセスへ • 2010年以降: そして伝説へ • なんかすごいらしいけど、実際に何を意味するかわからない -
アラン・ケイのオブジェクト指向 • プログラミング技術ではないのでは? • Smalltalkは商用化時にSimula形式のオブジェクト機能を導入 • 職業プログラマに使いやすくなった • 「自分の手を離れた」といっている •
エンドユーザーコンピューティングの研究 • エンドユーザーがコンピュータを使うためのプログラミング • 定義が断片的 • 職業プログラマの開発の指針にするのは難しそう • 実用化には再定義や再構築が必要 • 結局、その人の手法になる • ということでout-of-scope -
代表的なオブジェクト指向手法 • スリーアミーゴズの手法 • OMT(オブジェクトモデル化技法) • ランボー • オブジェクトモデリング •
OOSE(オブジェクト指向ソフトウェア工学) • ヤコブソン • ユースケースによる分析 • ブーチ法 • ブーチ • 開発プロセス • 他にもコード/ヨードンのOOA/OOD、Shlaer/Mellor法など -
なぜ多くの手法が現れたのか • 分析からコードまで同じモデルが使える • 気難しい継承を扱うために手法が必要 • クラスと継承を軸にそれぞれ関心ある開発領域をまとめる • 分析、プロジェクト管理、etc •
オブジェクト指向と名前をつけたら儲かる • 研究に予算がつく • 本が売れる • ソフトウェアが売れる • ソフトウェア工学が全部オブジェクト指向に • ソフトウェア工学の名前が浸透しなくなった • この会も「ソフトウェア工学カンファレンス」が適切(人がこない) -
オブジェクト指向手法の欠点 • ウォーターフォールが前提となっている • 分析→設計→実装 • 分析と実装で同じ記号が使える部分は実装の前倒しでしかない • 図によるコーディングでしかない •
アーキテクチャが組み込まれていない • ネットワーク普及前なので1台で処理する前提になってしまっている • RDBMSとのインピーダンスミスマッチ • 複雑なサーバー構成に対応できない • 現実にはアーキテクチャが決まり実装モデルが決まる • アプリケーションを一体で構築する -
手法からプロセスへ • 各工程ごとの成果物を定義するとウォーターフォールになる • ウォーターフォールを脱すると各工程での成果物の定義が難しい • サービス成長にあわせてアーキテクチャの模索も必要になる • コードの形だけを事前に決めることが難しい •
よいソフトウェアはよいプロセスから生まれる • CMMの流行 • ラショナルの統一手法も統一プロセスになった • コンピューティングリソースを開発プロセスにまわせるように なった • 継続的インテグレーション • Gitによる開発プロセス(昔はリポジトリのコピーなんて考えれなかった) -
オブジェクト指向は継承によるプログラミング • ストラウストラップ(C++作者) • 「オブジェクト指向プログラミングは継承を使うプログラミングだ」 • ランボー(オブジェクト指向モデリング技術) • 「オブジェクト指向的アプローチに要求される特徴が正確に何か、と いうことに関してはいくつかの議論があるが、それらの議論は普通以
下の4つの面を含んでいる。すなわち、アイデンティティ、分類、多相 性、そして継承である」 • ブーチ(ブーチ法) • 「もし言語が継承を直接サポートしないならば、それはオブジェクト 指向とは言えない」 -
継承ではない条件を考える • method(obj)をobj.method()と書ければオブジェクト指向 • 大騒ぎするほどの効果があるか疑問 • クラスを使えばオブジェクト指向 • 構造体でよさそう? •
データと関数を一緒に書ければオブジェクト指向 • 抽象データ型 • オブジェクトを作ればオブジェクト指向 • static避けでオブジェクト指向ロンダリングのためのオブジェクト作る • ソフトウェアをうまく作る工夫はすべてオブジェクト指向 • ソフトウェア工学のブランディングの失敗・・・ -
オブジェクトと関数の関係 • オブジェクトと関数は可換 • 属性をもたずメソッドが一つのオブジェクトは関数として扱える • Javaのラムダ式 • 名前を与えて要素を返す関数はオブジェクトとして扱える Function<String,
Object> myObj(int a, int b) { return name -> switch(name) { case "a" -> a; case "b" -> b; case "mul" -> a * b; case "add" -> a + b; default -> throw new RuntimeException(); }; } class MyFunc() { int apply(int a, int b) { return a + b; } } -
テンプレートメソッド • ポリモーフィズムの基本 abstract class Base { abstract void before();
abstract void after(); void proc() { before(); println("はろー"); after(); } } class School extends Base { @Override void before() { println("起立"); } @Override void after() { println("着席"); } } -
高階関数による実装 • 高階関数だとすっきり書ける void proc(Runnable before, Runnable after) { before.run();
println("はろー"); after.run(); } proc(() -> println("起立"), () -> println("着席")); ※ Runnableは引数戻り値なしの関数を表すのに使われる -
ポリモーフィズムは事前評価での条件分岐 • 条件をどのように集約するか • 次のcartの合計処理を考える • PackedとBulkで計算処理が違う sealed interface Type
{ /** 量り売り */ record Bulk(int price, int unit) implements Type {} /** パッケージ */ record Packed(int price) implements Type {} } record Item(Type type, int amount) {} var cart = List.of( new Item(new Packed(300), 3), new Item(new Bulk(250, 100), 230)); -
switchで条件分岐 • switchで条件分岐 var cart = List.of( new Item(new Packed(300),
3), new Item(new Bulk(250, 100), 230)); int total = cart.stream() .mapToInt(item -> switch(item) { case Item(Packed(int price), int amount) -> price * amount; case Item(Bulk(int price, int unit), int amount) -> price * amount / unit; }).sum(); -
継承で条件分岐 • 継承を使うとスッキリかけるように見える • Typeが増えても計算部分を変更しなくていい sealed interface Type { int
calc(int amount); /** 量り売り */ record Bulk(int price, int unit) implements Type { int calc(int amount) { return price * amount / unit; } } /** パッケージ */ record Packed(int price) implements Type { int calc(int amount) { return price * amount; } } } var cart = List.of( new Item(new Packed(300), 3), new Item(new Bulk(250, 100), 230)); int total = cart.stream() .mapToInt(item -> item.type().calc(item.amount())) .sum(); -
継承を使うときの問題 • 処理を追うために複数のクラスを見る必要がある • 条件がTypeではないときにきれいに書けなくなる • 時間帯とか • Typeの中で条件分岐が必要になる •
把握していないTypeがあると困ることがある • 業務処理では処理が一箇所にまとまってるほうがいい -
オブジェクト指向は状態管理技術 • 状態管理を共通化する • 例 • I/O • List abstract
class IO { boolean opened; void open() { if (opened) return; openImpl(); opened = true; } void close() { if (!opened) error; closeImpl(); opened = false; } abstract void openImpl(); abstract void closeImpl(); } class MyIO extends IO { @Override void openImpl() { println("開いた!"); } @Override void closeImpl() { println("閉じた!"); } } -
状態管理はモジュール境界に現れる • 状態管理はモジュールの端点に現れる • 例: DOM、HTTP、DB接続では状態管理が必要 • 中間の処理はステートレス GUI ロジック
データ ストア ユーザー アプリケーション DOM HTTP DB接続 -
端点にオブジェクト指向を使う • DBConnectionを継承してMySQLConnectionやOracleConnection を実装するとオブジェクト指向がハマる • ただし、MySQLConnectionやOracleConnectionは世界にひとつだけあ ればいい • ブラウザがGUIの状態管理をすればGUIフレームワークは ステートレスにできる
• 構造化されたGUI定義とイベントハンドラ • ブラウザの実装にはオブジェクト指向がハマるけど技術的には世界に ひとつあればいい(社会的には複数あったほうがいい) -
部分型はオブジェクト指向か? • 確かに部分型は継承の重要な機能 • オブジェクト指向は部分型以外の型機能に触れていない • 関数型 • 総称型(ジェネリクス) •
部分型にも種類がある • 継承の部分型は明示的に基底型を指定する • 公称型 • 継承=公称型+属性拡張 • 指定した要素を持っていれば部分型とみなすやりかたもある • 構造的部分型 • ダックタイピング -
部分型はオブジェクト指向か • 公称型はオブジェクト指向と呼んでもいいかもしれないけど 型理論の一部とみなすほうが豊かな議論が可能 • 公称型という仕組み自体はオブジェクト指向から独立できる(ハズ) • 初心者は公称型のコンセプトだけわかってれば当面Javaつかえる(ハズ) • interfaceの実装は必ずしもオブジェクト指向とは考えれない
• 代数的データ型の直和型の実現と考えれる • 例: Type型はBulk型かPacked型かどちらかになる sealed interface Type { /** 量り売り */ record Bulk(int price, int unit) implements Type {} /** パッケージ */ record Packed(int price) implements Type {} } -
クラスのデータ性とモジュール性 • クラスにはデータ型としての性質とモジュールとしての性質 • データ型としてのクラス • インスタンスが複数つくられる • モジュールとしてのクラス •
シングルトンであることが多い • サブシステムになる • 処理のあつまり class Name { String first; String last; Name(String first, String last) { this.first = first; this.last = last; } String toString() { return first + " " + last; } } @RestController class Greeting { @GetMapping("/hello") String hello() { return "hello"; } @GetMapping("/myname") Name myName() { return new Name("Naoki", "Kishida"); } } -
純粋データ型を考える • 純粋関数 • 副作用をもたない • 同じ引数を与えると同じ戻り値が返る • 純粋データ型(造語) •
データ型の外に副作用をもたない • 同じ属性を持つデータのメソッドに同じ引数を与えると同じ戻り値が 返る -
アプリケーションをレイヤー化する • モジュールとデータの分離 • アプリケーション処理はステートレス • モジュールは関数の集まり • 関数を分割するときはレイヤーに •
無限ループの回避 • レイヤー上をデータが流れる • データ指向プログラミング -
まとめ • オブジェクト指向は継承を使うプログラミングであり状態管理 • 継承以外はオブジェクト指向以外の言葉が使える • 用語のブレがないので技術コミュニケーションしやすい • アプリケーションではオブジェクト指向の出番はほとんどない •
端点で状態管理をすれば中間はステートレス=関数的 • フレームワークやミドルウェアでは必要 • オブジェクト指向を勉強してフレームワークやミドルウェアを作ろう