OOP とトップダウンの構造化プログラミングの設計上の違いとしてわかりやすいのは、 フローごとの動作の差異をどこが担保するかだと思います。
決済で、キャリア決済とクレジットカードによる決済を実装しようとしたとき、 トップダウンの場合、
case payment_method
when :carrier
do_carrier_payment
when :creditcard
do_creditcard_payment
end
でフローを分岐しておいて共通する処理は内部で共通のモジュールを呼び出す形で書くと思います。
また、構造化プログラミングでよく見かける悪い書き方だと共通の処理をダーッと書いて キャリアとクレカの違いの部分だけ都度フラグを見て挙動を変えます。
get_payment_amount
if (payment_method == :carrier)
call_carrier_payment_api(payment_amount) if in_limit? and valid_user?
elsif (payment_method == :creditcard)
call_authorize_api(payment_amount)
payment_commit_api(payment_amount)
end
... # 引き続き共通の後続する手続き
一方、OOP の場合、二つの決済手段があるということをそのままコードで書き、 両者に共通のプロトコルをまとめてフローを書きます。
共通のプロトコルのほうはこのようになります。
def payment(payment_amount, payment_method)
payment_method.commit(payment_amount) if payment_method.commitable?(payment_amount)
end
それぞれの決済手段ごとの実装の差異は payment_method へ渡される、 キャリア決済クラスのインスタンスやクレカ決済クラスのインスタンスとして表現されます。
class CarrierPayment
def commit payment_amount
call_carrier_payment_api(payment_amount)
end
def commitable? payment_amount
in_limit?(payment_amount) and valid_user?
end
end
class CreditCardPayment
def initialize card_number
@card_number = card_number
end
def commit payment_amount
payment_commit_api(payment_amount)
end
def commitable?
status = call_authorize_api(@card_number, payment_amount)
authorized?(status)
end
end
こうすると、トップダウンのプログラミングでコード値による条件分岐として実装されていた 決済手段のバリエーションが、 それぞれのオブジェクトによるポリモーフィズムとして実装されます。 これは決済手段が増えたときの結合度の低さにつながり、 保守性の高いプログラムを書きやすくなります。 こんな風に発想の起点が違うのが一番の違いかなと思ってます。
とくに、クラスに注目するのではなくて、 「payment_method という変数に束縛されるインスタンスによって挙動が実行時に動的に決まる」 という点が道具としてなじむと、OOP に慣れたといえる気がします。