liskov substitution principle(ells §10.5) · 2013. 8. 8. · dependency injection liskov...

21
Liskov Substitution Principle(ELLS §10.5) © 2012 Armando Fox & David Patterson Licensed under Creative Commons Attribution- NonCommercial-ShareAlike 3.0 Unported License

Upload: others

Post on 19-Aug-2020

15 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Liskov Substitution Principle(ELLS §10.5)

© 2012 Armando Fox & David Patterson Licensed under

Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported

License

Page 2: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Liskov Substitution: Subtypes can substitute for base types

• Current formulation attributed to (Turing Award winner) Barbara Liskov

• Let’s see an example

•  Type/subtype != class/subclass With duck typing, substitutability depends on how collaborators interact with object

“A method that works on an instance of type T, should also work on any subtype of T”

http://pastebin.com/nf2D9RYj

Page 3: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Contracts • “Prefer composition & delegation over inheritance” • If can’t express consistent assumptions about “contract” between class & collaborators, likely LSP violation • Symptom: change to subclass requires change to superclass (shotgun surgery)

Square

@rect area(), perimeter()

Rectangle

width, height area(), perimeter()

Rectangle

area, perimeter

width, height

Square

make_twice_as_wide_as_high

Page 4: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Only (a) is true ☐

Only (b) is true ☐

Both (a) and (b) are true ☐

Neither (a) nor (b) is true ☐

*

(a) In duck-typed languages, LSP violations can occur even when inheritance is not used (b) In statically-typed languages, if the compiler reports no type errors/warnings, then there are no LSP violations

http://pastebin.com/rLguzt8X

Page 5: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Dependency Injection(ELLS §10.6)

© 2012 Armando Fox & David Patterson Licensed under

Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported

License

Page 6: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Dependency Inversion & Dependency Injection

• Problem: a depends on b, but b interface & implementation can change, even if functionality stable • Solution: “inject” an abstract interface that a & b depend on • If not exact match, Adapter/Façade • “inversion”: now b (and a) depend on interface, vs. a depending on b • Ruby equivalent: Extract Module to isolate the interface

SessionStore

Database

read_from_db() store_in_db()

SessionMgr

get_session() store_session()

«interface»SessionStore

Database

Page 7: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

DIP in Rails: example

• What’s wrong with this in a view: - @vips = User.where('group="VIP"')

• A little better: - @vips = User.find_vips

• Happiness: # in controller @vips = User.find_vips

ActionView

User

User.where(…)

ActionView

@vips

Controller

User model

@vips

Page 8: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Injecting Dependencies with the Adapter pattern

• Problem: client wants to use a “service”... • service generally supports desired operations • but the API’s don’t match what client expects • and/or client must interoperate transparently with multiple slightly-different services • Rails example: database “adapters” for MySQL, Oracle, PostgreSQL, ...

ActiveRecord::Base

connection()

AbstractAdapter

connection()

MySQLAdapter

connection() mysql_connection() MySQL

Delegation Overriding

Page 9: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Example: Supporting External Services

• I use external services for email marketing • Both have RESTful API’s • Similar features • Maintain multiple lists, add/remove user(s) from list(s), change user’s opt-in status, ...

* Disclaimer: logos © or ™ their respective holders. This example is for education only and doesn’t imply endorsement, etc.—duh.

Customer

@email_list

EmailList

opt_in() opt_out()

update_email()

MailchimpList

subscribe() unsubscribe()

update_member()

Page 10: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Related: Façade

• In fact, we only use a subset of much more elaborate API’s • Initialization, list management, start/stop campaign... • So our adapter is also a façade • may unify distinct underlying API’s into a single, simplified API

MailchimpList

subscribe() unsubscribe()

update_member()

Connection mgt.

Member mgt.

List creation

Campaign mgt.

Report generator

List exporter

Page 11: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Related: Null object

• Problem: want invariants to simplify design, but app requirements seem to break this • Null object: stand-in on which “important” methods can be called @customer = Customer.null_customer @customer.logged_in? # => false @customer.last_name # => "ANONYMOUS" @customer.is_vip? # => false

EmailList opt_in()

opt_out() update_email()

MailchimpList subscribe()

unsubscribe() update_member()

http://pastebin.com/RBuvPMkR

Page 12: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Related: Proxy

• Proxy implements same methods as “real” service object, but “intercepts” each call • do authentication/protect access • hide remote-ness of a service • defer work (be lazy) • Rails example: association proxies (eg Movie.reviews)

Page 13: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

The controller under test is tightly coupled to the model ☐

In a static language, we'd have to use DI to achieve the same task in the testing framework.

Both of the above are true ☐

Neither the first nor the second statement is true ☐

*

In RSpec controller tests, it's common to stub ActiveRecord::Base.find, an inherited method. Which statements are true of such tests:

Page 14: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Demeter Principle + Example

• Only talk to your friends...not strangers. • You can call methods on • yourself • your own instance variables, if applicable • But not on the results returned by them. • Solutions: • replace method with delegate • Separate traversal from computation (Visitor) • Be aware of important events without knowing implementation details (Observer)

http://pastebin.com/NRSkHstN

Page 15: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Observer

• Problem: entity O (“observer”) wants to know when certain things happen to entity S (“subject”) • Design issues • acting on events is O’s concern—don’t want to pollute S • any type of object could be an observer or subject—inheritance is awkward

•  Example use cases •  full-text indexer wants to know about new post (e.g. eBay, Craigslist) •  auditor wants to know whenever “sensitive” actions are performed by an admin

Page 16: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Example: Maintaining Relational Integrity

• Problem: delete a customer who “owns” previous transactions (i.e., foreign keys point to her) • My solution: merge with “the unknown customer” • ActiveRecord provides built-in hooks for Observer design pattern

class CustomerObserver < ActiveRecord::Observer! observe :customer # actually not needed (convention)! def before_destroy ... end!End! # in config/environment.rb config.active_record.observers = :customer_observer

Page 17: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Yes...replace with Order#customer_name which delegates to Customer#name

You can make a case for either of the above No…by using belongs_to we’re already exposing info about the Customer anyway

Yes...but probably reasonable to just expose object graph in the view in this case

*

Suppose Order belongs to Customer, and view has @order.customer.name!… is this a Demeter violation?

Page 18: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

A Few Patterns Seen in Rails • Adapter (database connection) • Abstract Factory (database connection) • Observer (caching—Chapter 12) • Proxy (AR association collections) • Singleton (Inflector) • Decorator (AR scopes, alias_method_chain) • Command (migrations) • Iterator (everywhere) • Duck typing simplifies expressing and “plumbing” most of these by “weakening” the relative coupling of inheritance

Page 19: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

SOLID Caveat

• Designed for statically typed languages, so some principles have more impact there • “avoid changes that modify type signature” (often implies contract change)—but Ruby doesn’t really use types • “avoid changes that require gratuitous recompiling”—but Ruby isn’t compiled • Use judgment: goal is deliver working & maintainable code quickly

Page 20: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Summary • Design patterns represent successful solutions to classes of problems • Reuse of design rather than code/classes • A few patterns “reified” in Rails since useful to SaaS • Can apply at many levels: architecture, design (GoF patterns), computation • Separate what changes from what stays the same • program to interface, not implementation • prefer composition over inheritance • delegate! • all 3 are made easier by duck typing • Much more to read & know—this is just an intro

Page 21: Liskov Substitution Principle(ELLS §10.5) · 2013. 8. 8. · Dependency Injection Liskov Substitution ☐ Open/Closed Principle ☐ ☐ ☐ * Rails’ ActiveRecord module defines

Dependency Injection

Liskov Substitution

Open/Closed Principle ☐

*

Rails’ ActiveRecord module defines an AbstractAdapter for connecting to databases. Subclasses of AbstractAdapter exist for each database type and can be added for new databases; when the app starts, the correct one is instantiated based on config/database.yml. Which SOLID principle is NOT illustrated by this example:

Demeter Principle