Getting loose coupling

Designing and Maintaining Software (DAMS)
 Louis Rose

Recap: DIP “High-level modules should not depend on low-level modules. Both should depend on abstractions.”
 
 “Abstractions should not depend on details. Details should depend on abstractions.” - Bob Martin
 http://www.objectmentor.com/resources/articles/dip.pdf

Tactics Inject dependencies to avoid coupling
 with specific implementations Invert control to avoid coupling
 with implementations “Tell, Don’t Ask” to avoid coupling
 with intermediaries Introduce gateways to avoid coupling
 with external services

Inject Dependencies class Pizza def initialize(temperature, duration) @temperature = temperature @duration = duration end def bake Oven.new(@temperature, @duration).cook(self) end end Pizza.new(200, 30).bake

Inject Dependencies class Pizza def initialize(temperature, duration) @temperature = temperature @duration = duration end def bake Oven.new(@temperature, @duration).cook(self) end end Pizza.new(200, 30).bake

Inject Dependencies class Pizza def initialize(oven) @oven = oven end def bake @oven.cook(self) end end Pizza.new(Oven.new(200, 30)).bake
 Pizza.new(Barbeque.new(:smoky, 30)).bake

Invert Control connection = Connection.new(@credentials) connection.open connection.send("foo") connection.send("bar") connection.flush connection.close

Invert Control connection = Connection.new(@credentials) connection.open connection.send(“foo") connection.send("bar") connection.flush connection.close

Invert Control Connection.perform(@credentials) do |connection| connection.send("foo") connection.send("bar") end

Client code no longer specifies when it’s called
 Hollywood Principle: “Don’t call us, we’ll call you”

Connection.perform? class Connection def self.perform(@credentials) connection = Connection.new(@credentials) connection.open yield connection.flush connection.close end end

Recap: Law of Demeter “Talk only to your neighbours” allowance yourself

example self

your own toys

formatter

toys you’ve been given

printable

toys you’ve made yourself

out

class Printer attr_reader :formatter def print(printable) out = TempFile.new
 ... end end

Tell, Don’t Ask class Courier def deliver_to(customer, parcel) drive_to(customer.address) customer.give(parcel) customer.wallet.withdraw(self.cost) end end

Tell, Don’t Ask class Courier def deliver_to(customer, parcel) drive_to(customer.address) customer.give(parcel) customer.wallet.withdraw(self.cost) end end

Tell, Don’t Ask class Courier def deliver_to(customer, parcel) drive_to(customer.address) customer.give(parcel) customer.wallet.withdraw(self.cost) end end

Tell, Don’t Ask class Courier def deliver_to(customer, parcel) drive_to(customer.address) customer.give(parcel) customer.wallet.withdraw(self.cost) end end

Courier is now coupled to how a customer stores their money!


Tell, Don’t Ask class Courier def deliver_to(customer, parcel) drive_to(customer.address) customer.give(parcel) customer.wallet.withdraw(self.cost) end end

Courier is now coupled to how a customer stores their money!
 But, really, a courier only cares that they get paid, not how.

Tell, Don’t Ask class Courier def deliver_to(customer, parcel) drive_to(customer.address) customer.give(parcel) customer.request_payment(self.cost) end end

Courier is now coupled to how a customer stores their money!
 But, really, a courier only cares that they get paid, not how.

Tell, Don’t Ask class Courier def deliver_to(customer, parcel) drive_to(customer.address) customer.give(parcel) customer.request_payment(self.cost) end end
 
 class Customer def request_payment(amount) @wallet.withdraw(amount) end end


 But, really, a courier only cares that they get paid, not how.

Tell, Don’t Ask class Courier def deliver_to(customer, parcel) drive_to(customer.address) customer.give(parcel) customer.request_payment(self.cost) end end
 
 class Customer def request_payment(amount) @wallet.withdraw(amount) end end


 But, really, a courier only cares that they get paid, not how.

Introduce Gateways class Order def bill braintree_id = Braintree.find_user(@user.email).braintree_id Braintree.charge(braintree_id, @amount) end end class Refund def run transaction_id = Braintree.find_transaction(@order.braintree_id) Braintree.refund(transaction_id, @amount) end end

Order and Refund are coupled to this payment processor (Braintree).
 What if we want to use a Braintree2 gem? Or switch to Stripe?

Introduce Gateways class Order def bill user_id = PaymentGateway.find_user(@user.email).id PaymentGateway.charge(user_id, @amount) end end class Refund def run transaction_id = PaymentGateway.find_transaction(@order) PaymentGateway.refund(transaction_id, @amount) end end

Introduce Gateways class Order def bill PaymentGateway.charge(@user, @amount) end end class Refund def run PaymentGateway.refund(@order, @amount) end end

Prefer gateways that have interfaces closer to the client code.
 Changes to the external service are then isolated in the gateway.

Summary Dependencies are powerful yet perilous Depend on abstractions using:
 - dependency injection
 - inversion of control
 - tell, don’t ask
 - gateways

Avoid Ordering Dependencies class Oven def initialize(temperature, duration) @temperature = temperature @duration = duration end end Oven.new(200, 30)

Avoid Ordering Dependencies class Oven def initialize(temperature:, duration:) @temperature = temperature @duration = duration end end Oven.new(temperature: 200, duration: 30) Oven.new(duration: 30, temperature: 200)

Avoid Ordering Dependencies class Oven def initialize(temperature: 200, duration:) @temperature = temperature @duration = duration end end Oven.new(temperature: 200, duration: 30) Oven.new(duration: 30, temperature: 200)
 Oven.new(duration: 30)

Designing and Maintaining Software (DAMS) - GitHub

Getting loose coupling. Designing and Maintaining Software (DAMS). Louis Rose ... should not depend on low-level modules. Both should depend on abstractions.” “Abstractions should not depend on details. Details should depend on abstractions.” - Bob Martin http://www.objectmentor.com/resources/articles/dip.pdf ...

107KB Sizes 0 Downloads 105 Views

Recommend Documents

Designing and Maintaining Software (DAMS) - GitHub
ASTs are tree data structures that can be analysed for meaning (following JLJ in SYAC 2014/15) ... More Cohesive. Avoids Duplication. Clearer. More Extensible.

Designing and Maintaining Software (DAMS) - GitHub
%w.rack tilt date INT TERM..map{|l|trap(l){$r.stop}rescue require l};. $u=Date;$z=($u.new.year + 145).abs;puts "== Almost Sinatra/No Version has taken the stage on #$z for development with backup from Webrick". $n=Module.new{extend. Rack;a,D,S,q=Rack

Designing and Maintaining Software (DAMS) - GitHub
Clear Documentation. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Bad documentation. Misleading or contradictory find_customer(id). CustomerGateway. Used to look up a customer by their customer number. Page 3. Bad documentation. Red

Designing and Maintaining Software (DAMS) - GitHub
R&D: sketch habitable solutions on paper, using UML. 4. Evaluate solutions and implement the best, using TDD. Probably start again at 3. 5. Give to the product owner to validate. Probably start again at 1. 6. Put into production for customers to eval

Designing and Maintaining Software (DAMS) - GitHub
Observers. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Page 3. Delivery people need to know when pizzas are ready class Pizza def initialize(delivery_person). @delivery_person = delivery_person end def bake cook # blocking call. @d

Designing and Maintaining Software (DAMS) - GitHub
When we are testing the way that a unit behaves when a condition is met, use a stub to setup the condition. Solution: use stubs for queries class Subscription ... def bill(amount) unless payments.exists(subscription_id: id) payments.charge(subscripti