Dependency Injection (ESaaS §11.6) © 2013 Armando Fox & David Patterson, all rights reserved.
-
Upload
emil-collins -
Category
Documents
-
view
234 -
download
2
Transcript of Dependency Injection (ESaaS §11.6) © 2013 Armando Fox & David Patterson, all rights reserved.
Dependency Injection(ESaaS §11.6)
© 2013 Armando Fox & David Patterson, all rights reserved
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 a Module
to isolate the interface
SessionStore
Database
read_from_db()store_in_db()
SessionMgr
get_session()store_session()
«interface»SessionStore
Database
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_vipsIndependent of how VIPs are represented
in model!
ActionView
User
User.where(…)
ActionView
@vips
Controller
User model
@vips
Injecting Dependencies with the Adapter Pattern
• Problem: client wants to use a “service”...– service generally supports desired operations– but the APIs 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
Example: Supporting External Services
• Suppose RottenPotatoes adds email marketing – send discount emails to moviegoers
• Use external service MailerMonkey
• Suppose very popular, you want to extend to send emails to Amiko friends
• Suppose Amiko exposes a different API– Adaptor:
http://pastebin.com/ZdhcYb7w
http://pastebin.com/8PHBpm5k
http://pastebin.com/Eimsw8ZF
Related: Façade
• In fact, we only use a subset of much more elaborate API– Initialization, list management, start/stop
campaign...• So our adapter is also a façade
– may unify distinct underlying APIs into a single, simplified API
MailchimpList
subscribe()unsubscribe()
update_member()
Connection mgt.
Member mgt.
List creation
Campaign mgt.
Report generator
List exporter
Related: Façade
9
END
More Adapter-Like Patterns(ESaaS §11.6)
© 2013 Armando Fox & David Patterson, all rights reserved
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? # => [email protected]_name # => "ANONYMOUS"@customer.is_vip? # => false
EmailList
opt_in()opt_out()
update_email()
MailchimpList
subscribe()unsubscribe()
update_member()
FakeEmailList
opt_in()opt_out()
update_email()
Example: Supporting External Services
• Suppose RottenPotatoes adds email marketing – send discount emails to moviegoers
• We want to disable email sending from time to time
http://pastebin.com/js6C67mJ
http://pastebin.com/avRQAgZc
Related: Proxy
• Proxy implements same methods as “real” service object, but “intercepts” each call– do authentication/protect access– defer work (be lazy)– Rails example: r= @Movie.reviews
• Respond to all collection methods without querying the database, except when it has to
Singleton: Ensuring There’s Only One of Something
• Technically, a class that provides only 1 instance, which anyone can access
• Ruby supports a very elegant way to do this, using singleton classes (no relation!)– A global $variable? Others can reassign it– A CONSTANT? Can’t control when it’s initialized
• The singleton object is in every respect a member of the base class, but immutable and singular
• Singleton Example: http://pastebin.com/RBuvPMkR
17
END
Demeter Principle(ESaaS §11.7)
© 2013 Armando Fox & David Patterson, all rights reserved
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– Be aware of important events without knowing
implementation details (Observer) – Separate traversal from computation (Visitor)
http://pastebin.com/NRSkHstN
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
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 ... endend
# in config/environment.rbconfig.active_record.observers = :customer_observer
Visitor
• When traversing data structure (DS), provide a callback method to execute for each DS member– Visit each element without knowing how the data
structure is organized
25
END
Dealing With Collections:Composite
© 2013 Armando Fox & David Patterson, all rights reserved
Composite
• What: component whose operations make sense on both individuals & aggregates
• Example: regular tickets, VIP tickets, subscriptions• What do they have in common?
– Has a price– Can be added to an order
• What’s different?– Regular & VIP Tickets
are for specific show– Subscription has to track
which tickets it “owns”
Composite
• What: component whose operations make sense on both individuals & aggregates
• Example: regular tickets, VIP tickets, subscriptions• What do they have in common?
– Has a price– Can be added to an order
• What’s different?– Regular & VIP Tickets
are for specific show– Subscription has to track
which tickets it “owns”
1. Common base class: what stays the same?
2. Individual object (may have
>1 of these)
3. Composite (aggregate) of objects
Simplified Implementation
class RegularTicket < Ticket attr_accessor :price, :show_date def add_to_order ... end def refund ... endend
class MultiTicket < Ticket def initialize @tickets = [] super end attr_reader :tickets def add_tickets(t) @tickets += t end def price @tickets.sum { |t| t.price } end def add_to_order @tickets.each { |t| t.add_to_order } end
Ticket
price()add_to_order()
RegularTicket
show_date()
MultiTicket
add_tickets()tickets()
• Note, not all methods necessarily make sense for leaf vs. aggregate!
• In this implementation, subscriptions can nest; may or may not be desirable
VIPTicket
show_date()
Simplified Implementation
class RegularTicket < Ticket attr_accessor :price, :show_date def add_to_order ... end def refund ... endend
class MultiTicket < Ticket def initialize @tickets = [] super end attr_reader :tickets def add_ticket(t) @tickets += t end def price @tickets.sum { |t| t.price } end def add_to_order @tickets.each { |t| t.add_to_order } end
class Subscription < MultiTicket def <<(subscription) add_tickets(subscription) endend
class Customer has_many :tickets ... self.tickets << new_ticketend
We can use Ruby’s open classes to allow “overloaded” methods like << to do the right thing as well:
Composite and Single-Table Inheritance
• Rails Single-table inheritance (Google it) stores objects of different subclasses (but same parent class) in same table– Rails automatically manages a type column that
denotes the Ruby object’s (sub)class– Can define separate validations, associations, etc. on
each subclass
• Can support Composite & some other patterns class Ticket < ActiveRecord::Baseclass RegularTicket < Ticketclass VIPTicket < Ticketclass Subscription < Ticket
34
END
Plan-And-Document Perspective on Design Patterns
(Engineering Software as a Service §11.8)
35© 2013 Armando Fox & David Patterson, all rights reserved
P&D Design Patterns?
• What are the Pros & Cons to Plan-and-Document from Design Pattern perspective?
• In which is it easier to use Design Patterns: Plan-and-Document or Agile?
36
P&D Approach to Design Patterns
• Careful planning can result in good SW architecture– P&D a.k.a. “Big Design Up Front”
• Break Software Requirements Specification (SRS) into problems
• For each task, looks for design patterns that match– Then to patterns for subproblems
• Design Reviews can help
37
Agile Approach to Design Patterns
• Agile critique: encourages developers to start coding without any design– Rely too much on refactoring later
• P&D critique: No code until design complete => No confidence design implementable, matches customer needs– When coding starts, design must change
38
How Much Design Up Front?
• Can plan for storage for SaaS app despite on 1st BDD/TDD tests don’t use database
• Should think about horizontal scaling (Ch 12) early on
• Agile Advice: If previously done project that has some design constraint or element, OK to plan for in similar project, as experience likely leads to reasonable design decisions
39
40
END
Getting Started with Design Patterns
• GoF distinguishes design patterns from frameworks– Patterns are more abstract, narrower in focus,
not targeted to problem domain• Nevertheless, frameworks great way for
novice to get started with design patterns– Gain experience on creating code based on
design patterns by examining patterns in frameworks instantiated as code
43
44
END
Design Patterns & SOLID Wrap-up
(ESaaS §11.9-11.10)
© 2013 Armando Fox & David Patterson, all rights reserved
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
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
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
49
END