From: Hideto ISHIBASHI Date: 2000-06-13T01:14:53+09:00 Subject: [ruby-list:23186] Re: メソッドの中の動きを変える 石橋"rubyholic"秀仁です。コードを含むので長いです。 リファクタリング (refactoring) だと思って読んでください。 From: Shin-ichiro Hara Subject: [ruby-list:23166] Re: メソッドの中の動きを変える > 原です。 > > |石橋"rubyholic"秀仁です。 > > |つきつめると、継承どころか Mix-in さえ「やりすぎ」だと感じる > |ようになってきました。これはおおげさですが、「安易に」継承や > |Mix-in の採用を決めるのは、悪い設計への落し穴だと思います。 > > でも、Factory Method でも、操作の切替えに継承を用いるのではないでし > ょうか。実は良く分かってないんですけど Factory Method。(^^; GoF 本どおりに Factory Method を実装してみます。 # Application AbstractDB # ^ ^ # | inherits | inherits # +----------+---------+ +--+---+ # | | | | # MysqlApplication CsvApplication Mysql Csv # | | ^ ^ # +----------------------------+ | # uses +--------------------+ # uses class Application def createDB # abstract method end def restore @db = createDB @db.restore end end class MysqlApplication < Application def createDB Mysql.new end end class CsvApplication < Application def createDB Csv.new end end class AbstractDB def restore; open; read; close; end def open;end def read;end def close;end end class Mysql < AbstractDB def open; puts "open Mysql";end def read; puts "read Mysql";end def close; puts "close Mysql";end end class Csv < AbstractDB def open; puts "open Csv";end def read; puts "read Csv";end def close; puts "close Csv";end end foo = MysqlApplication.new #foo = CsvApplication.new foo.restore でも Ruby なら継承を使わない方法もあります。Application#restore と AbstractDB#restore をほかのクラスに吸収します。 class Application def setDB(db) @db = db end def restore @db.restore end end class Mysql def restore; open; read; close; end def open; puts "open Mysql";end def read; puts "read Mysql";end def close; puts "close Mysql";end end class Csv def restore; open; read; close; end def open; puts "open Csv";end def read; puts "read Csv";end def close; puts "close Csv";end end foo = Application.new foo.setDB(Mysql.new) #foo.setDB(Csv.new) foo.restore 継承がなくなりました。DB (Mysql, Csv) がクライアント (Application) に提供するインターフェースは #restore だけです。 observer.rb の #update と同じ考えかたですね。 クラスがクライアントに提供する interface を少なく制限することで、 継承を使わなくても整合性の問題などに悩まされることはありません。 # もちろんトレードオフです。継承が適した場合もあります。 <継承と委譲> ところで、Mysql と Csv には同じようなコードがありますね。 同じようなコードをまとめるには継承を使うことがあります。しかし、 継承はホワイトボックス的再利用です。それに対して、委譲 (包合) は ブラックボックス的再利用です。 たとえば継承による再利用では、スーパクラスのあるインスタンス 変数が除去されたらサブクラスも書きかえる必要がありますよね。 委譲なら、クラスのインターフェースとリスポンシビリティ (責任) が変わらなければ、クライアントのコードを書きかえる必要がない。 つぎに Mysql と Csv は既存のクラスで変更できないと仮定します。 既存のクラスですから、メソッド名が都合よく open, read, close にそろっているはずありません。つぎのコードでは委譲で再利用します。 ちなみに GoF の Adapter パターンです。xInterface が Adapter です。 この仮定は既存のクラスを再利用するというだけではありません。 adaptee クラス (Mysql, Csv) の将来の変更が、client (Application) に波及しないようにする意味もあります。 # class Application は上と同じ class MysqlInterface # <> def restore db = Mysql.new db.connect db.query db.disconnect end end class CsvInterface # <> def restore csv = Csv.new csv.open csv.read csv.close end end class Mysql # <> def connect; puts "new Mysql";end def query; puts "query Mysql";end def disconnect; puts "disconnect Mysql";end end class Csv # <> def open; puts "open Csv";end def read; puts "read Csv";end def close; puts "close Csv";end end foo = Application.new foo.setDB(MysqlInterface.new) #foo.setDB(CsvInterface.new) foo.restore こんどは、Application が直接には Mysql や Csv を扱いません。 xInterface クラスが仲介し、Mysql と Csv のインターフェースを Application に適合 (adapt) させます (#restore を提供)。 さて、これまでに 3 つのコードを示しました。 将来の変更に強い柔軟な設計は最後のコードだと思います。 > ですか。こんな感じで、継承は使う時は使いますよね。ただ、使う > 場所を選ぼう、ということですか。 そうですね。継承は (よく切れるナイフのように) 強力で、危険です。 継承は "is-a" 関係と言われますけど、"is-a" には 2 つの意味が あります。それを混同すると危険です。 『UML モデリングのエッセンス』に解説 (6.11 分類と汎化) があります。 "is-a" には「分類」と「汎化」の場合があり、「汎化は推移的 (B ぼくは複数のクラスから共通性を抽出して汎化する方法 (ボトムアップ アプローチ) を好みます。さきにスーパクラスを定義してから、 サブクラスをいくつも定義するやりかた (トップダウンアプローチ) にはリスクがあります。そのリスクとは、早いうちにクラス階層を 確定するリスクです。 分析/設計が進むうちに継承関係が不適切だと分かることがあります。 将来を正確に予見するのは困難ですから、トップダウンが得意でない 人はボトムアップのほうが安全です。この点で動的言語は気楽です。 # しかしドメインレベルの概念的分析においては、トップダウン # アプローチで分析が軌道にのることもありますね。この話は # ソフトウェアクラスの分析/設計についての話です。 さて、スレッドの Subject は「メソッドの中の動きを変える」 ですけど、なぜここに至ったかを再検討することも有益です。 クラスを増やしたり減らしたりするより、すでにあるクラスの メソッドをいじくるほうが手軽に思えるものです。しかし、 オブジェクトの協調 (collaboration) を検討するほうが、 早く答えに近づくと思いますよ。 「メソッド」はオブジェクト (クラス) が「責任」を果たすための 手段にすぎない、と考えて、まずは複数のオブジェクト (クラス) の協調を設計することを考えるほうが良いと思います。 そのためには CRC カードという手法が有効だと思います。 # ぼくは CRC カードについて詳しいわけではなく、我流です。 # 日本語の解説は『UML モデリングのエッセンス』のコラムくらい? -- Hideto "rubyholic" ISHIBASHI http://www.rr.iij4u.or.jp/~hideto-i/ blade clone (yaiba) development: http://www.rr.iij4u.or.jp/~hideto-i/rb/yaiba/index.html