Upload
jan-suchal
View
2.971
Download
4
Embed Size (px)
DESCRIPTION
Prezentácia z Rubyslavy #4 (@tkramar, @jsuchal)
Citation preview
Postobjektovéprogramovanie v Ruby
@tkramar, @jsuchal
Paradigmamodel, súbor princípov, spôsob uchopenia
problému
Aké paradigmy poznáte?procedurálna (C, Basic, COBOL, ..)
zdieľané dáta, procedúry operujúce nad dátami
objektová (Ruby, Java, Python, ..)zapuzdrené dáta, metódy operujúce nad stavom objektu
funkcionálna (Lisp, Haskell, Scheme, ..)dáta = program, čisté výrazy, bez vedľajších efektov
logická (PROLOG)deklaratívne programovanie - fakty a predikáty, odvodzovanie
Objektidentita
stav
správanie
class Person @name = ''
def initialize(name) @name = name end
def say "Ahoj, ja som #{@name}" endend
PersonStav
@name
Správaniedef say "Ahoj, ja som #{@name}"end
Identitap = Person.new('Janko')o.object_id
Identitasú dva objekty rovnaké?
==
sú dva objekty rovnaké a rovnakého typu?eql?
sú dva objekty totožné?equals?
Identitap1 = Person.new('Janko') #=> #<Person:0x00000001b5c100 @name="Janko">p2 = Person.new('Janko')#=> #<Person:0x00000001b556e8 @name="Janko">
family = []family << p1family << p2
family.uniq#=> [#<Person:0x00000001b5c100 @name="Janko">, #<Person:0x00000001b556e8 @name="Janko">]
class Person def ==(other) other.name == @name end
def eql?(other) self == other endend
Identitap1 = Person.new('Janko') #=> #<Person:0x00000001b5c100 @name="Janko">p2 = Person.new('Janko')#=> #<Person:0x00000001b556e8 @name="Janko">
family = []family << p1family << p2
family.uniq#=> [#<Person:0x00000001b5c100 @name="Janko">, #<Person:0x00000001b556e8 @name="Janko">]
class Person def hash @name.hash endend
Identitap1 = Person.new('Janko') #=> #<Person:0x00000001b5c100 @name="Janko">p2 = Person.new('Janko')#=> #<Person:0x00000001b556e8 @name="Janko">
family = []family << p1family << p2
family.uniq#=> [#<Person:0x00000001b5c100 @name="Janko">]
Základné konceptyinheritance (dedenie)
method overloading (preťažovanie metód)
method overriding (prekonávanie metód)
Dedenieclass Instrument def tune ... endend
class Violin < Instrument def play tune "Taa daa" endend
Overloading v ruby nefungujeclass Instrument def play "Ta da" end
def play(octave) "Ta da #{octave}" endend
i = Instrument.newi.play #=> ArgumentError: wrong number of arguments (0 for 1)i.play('c dur') #=> "Ta da c dur"
Overridingclass Instrument def play "Ta da" endend
class Violin < Instrument def play "Tiii diii tiii" endend
v = Violin.newv.play #=> "Tiii diii tiii"
Polymorfiaclass Instrument def play endend
class Violin < Instrument def play "Taa daa" endend
class Drum < Instrument def play "Dum dum" endend
orchestra = [Violin.new, Violin.new, Drum.new]orchestra.each { |i| i.play }#=> "Taa daa", "Taa daa", "Dum dum"
Všetko v Ruby je Objekt
Aj nilObject.class #=> Classnil.class #=> NilClass
ActiveSupport tryp = Person.find(params[:id])
p.address.downcase #=> NoMethodError: undefined method `downcase' # for nil:NilClass
p.address.try :downcase #=> nil
Aj metódaclass Person def greet(other_person) "Ahoj #{other_person}" endend
m = Person.instance_method(:greet)m.class #=> UnboundMethodm.arity #=> 1m.name #=> :greetm.parameters #=> [[:req, :other_person]]
Aj triedaClass.class #=> Class
.. o metaprogramovaní inokedy
Modulydef Class < Module
Modul ako namespacemodule Rubyslava class OOP endend
module Pyvo class OOP endend
Rubyslava::OOP == Pyvo::OOP #=> false
Modul ako nositeľ kódumodule Rubyslava def hello "Rubyslava" endend
Mixinyclass RubyslavaAPyvo include Rubyslavaend
rp = RubyslavaAPyvo.newrp.hello #=> "Rubyslava"
class RubyslavaAPyvo extend Rubyslavaend
RubyslavaAPyvo.hello #=> "Rubyslava"
Viacnásobné dedenieclass RubyslavaAPyvo < Array include Rubyslava, Pyvoend
RubyslavaAPyvo.ancestors#=> [RubyslavaAPyvo, Rubyslava, Pyvo# Array, Object, Kernel, BasicObject]
Viditeľnosť v modulochmoduly zdieľajú všetko
inštančné premenné (@)
metódy
module AdditionalBehaviour def show greet endend
class Test include AdditionalBehaviour
def greet "hello" endend
Test.new.show #=> "hello"
module GreetingBehavior def greet "hello" endend
module RelyOnGreetingBehavior def show greet endend
class Test include GreetingBehavior, RelyOnGreetingBehaviorend
Test.new.show #=> "hello"
Zo životaEnumerable
acts_as_..
ActiveSupport::Concern
class Tree include Enumerable
def each(&block) leafs.each do { |n| yield n } endend
t = Tree.newt.mapt.injectt.sum...
class Post acts_as_taggableend
p = Post.find(1)p.tag_list #=> ["Ruby", "OOP"]
ActiveSupport::Concernmodule Annotatable extend ActiveSupport::Concern
included do has_many :notes, :as => :annotatable, :order => "created_at DESC" end
def most_annotated joins(:notes).group(:id).order("COUNT(*) DESC").first endend
class Project < ActiveRecord::Base include Annotatable, Importable, Versionableend
Idiómyjazykovo špecifické vzory
Ruby idiómybloky
a, b = b,a
a, b, c = array
(cachovanie) @var ||= ..
Blokytransaction do |transaction| transaction.rollback if error?end
Dir.chdir('/') doend
with_disabled_keys doend
with_nested_loop_plan doend
VzoryVšeobecné riešenie často sa opakujúcehoproblému. Nie je to knižnica, ani kód, skôr
šablóna.
"When I see patterns inmy programs, I consider it
a sign of trouble."(Paul Graham)
Vzory, ktoré v Rubynetreba programovať
SingletonProblém: Zabezpečiť jednotný prístup k zdrojom
(napr. databáza)Zlo, česť výnimkám (ActiveSupport::Inflections)
Singletonclass Logger def initialize @log = File.open("log.txt", "a") end
@@instance = Logger.new
def self.instance @@instance end
def log(msg) @log.puts(msg) end
private_class_method :newend
Logger.instance.log('message 1')
Singleton (lepšie)require 'singleton'
class Logger include Singleton
def initialize @log = File.open("log.txt", "a") end
def log(msg) @log.puts(msg) endend
Logger.instance.log('message 2')
IteratorProblém: Umožniť manipuláciu s kompozitnýmobjektom bez odhalenia jeho internej štruktúry
Iteratorclass Tree include Enumerable
def each(&block) leafs.each { |e| yield e } endend
DecoratorProblém: pridávanie funkcionality do triedy
počas behu programu
Dekorátor obalí pôvodnú triedu a kde trebarozšíri správanie
class StringDecorator < String def starts_with? substr # ... endend
title = StringDecorator.new("Rubyslava")
Decorator = open classesclass String def starts_with? substr # ... endend
"aye aye captain".starts_with? "pirate" #=> false
DelegatorProblém: Skrytie vnútorných závislostí
Delegatorinclude 'forwardable'class Car extend Forwardable
def_delegators :@car_computer, :velocity, :distance
def initialize @car_computer = CarComputer.new endend
c = Car.newc.velocityc.distance
ProxyProblém: Skrytie implementačných detailov
načítavania zdroja
Proxyrequire 'delegate'
class Future < SimpleDelegator def initialize(&block) @_thread = Thread.start(&block) end
def __getobj__ __setobj__(@_thread.value) if @_thread.alive?
super endend
google = Future.new do Net::HTTP.get_response(URI('http://www.google.com')).bodyendyahoo = Future.new do Net::HTTP.get_response(URI('http://www.yahoo.com')).bodyend
puts googleputs yahoo
Ďalšie užitočné vzory
Template methodProblém: Často sa opakujúci boilerplate kód.
Kostra programu, algoritmu.
class Game def play prepare_board initialize_score while not end_of_game? make_move end endend
class Carcassonne < Game def make_move tile = tiles.pick_random ... endend
class Agricola < Game def make_move peasant = select_peasant ... endend
CompositeProblém: V stromových štruktúrachzamaskovať implementačný detail:
uzly a listy.
class Person def say "#{@name}" endend
class Family attr_accessor :members
def initialize(members) @members = members end
def say @members.each { |m| m.say } endend
f = Family.new(Person.new('Janko'), Person.new('Tomas'))t = Family.new(Person.new('Ferko'), f)
t.say #=> "Ferko", "Janko", "Tomas"
StrategyProblém: Výber vhodného algoritmu
za behu programu
class FileLogger < Logger def log(message) File.open('a') { |f| f << message } endend
class DatabaseLogger < Logger def log(message) Log.create(:message => message) endend
StateProblém: Zmena správania objektu
v rôznych stavoch
class TrafficLight include AlterEgo
state :proceed, :default => true do handle :color do "green" end transition :to => :caution, :on => :cycle! end
state :caution do handle :color do "yellow" end transition :to => :stop, :on => :cycle! end
state :stop do handle :color do "red" end transition :to => :proceed, :on => :cycle! endend
light = TrafficLight.newlight.color # => "green"light.cycle!light.color # => "yellow"light.cycle!light.color # => "red"light.cycle!light.color # => "green"
ObserverProblém: Oddelenie core funkcionality
a špecifického správania v určitýchstavoch objektu.
class Comment < ActiveRecord::Baseend
class CommentObserver < ActiveRecord::Observer def after_save(comment) Notifications.deliver_comment("[email protected]", "New comment was posted", comment) endend
Zápachy v kóde(Bad smells)
Vytváranie vlastnéhotypového systému
is_a?, kind_of?
lepšie je respond_to?
class Container def add(node) if node.kind_of? Array node.each { |n| @nodes << n } else @nodes << node end endend
container.add(Tree.new('a', 'b')) #=> ???
class Container def add(node) unless node.respond_to? :each node = [node] end node.each { |n| @nodes << n } endend
container.add(Tree.new('a', 'b')) #=> ['a', 'b']
ProtipríkladActiveRecord::sanitize_sql_for_assignment
def sanitize_sql_for_assignment(assignments) case assignments when Array sanitize_sql_array(assignments) when Hash sanitize_sql_hash_for_assignment(assignments) else assignments endend
Visitor + typydef visit object method = "visit_#{object.class.name}" send method, object end
module Arel module Visitors class WhereSql < Arel::Visitors::ToSql def visit_Arel_Nodes_SelectCore o "WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" end end endend
Shotgun surgeryak niečo mením tak musím na viacerých
miestach
crosscutting concerns
AOP (AspectJ)
AspectJpointcut writableHeaderMethods() : execution(* (WritableRequestHeader|| WritableResponseHeader) .*(..));
before(HeaderWrapper header) : writableHeaderMethods() && this(header) { ...}
ActiveRecord::Observer
ActiveController::Caching::Sweeper
before/after/around filtre
Cachingclass DocumentController < ApplicationController caches_page :show caches_action :index
def show; end
def index; endend
Autorizáciaclass DocumentController < ApplicationController before_filter :find_document
def show # render @document end
private def find_document @document = Document.find_by_id(params[:id]) endend
class DocumentController < ApplicationController before_filter :find_document around_filer :authorize
def show # render @document end
private def find_document @document = Document.find_by_id(params[:id]) end
def authorize if current_user.can_view?(@document) yield else render :not_authorized end endend
Ťažko testovateľná triedaskúsiť si najprv napísať test
rcov
Duplicitný kódreek
metric_fu
rubymine
refaktoring
vzor Template
Často sa meniaci kódchurn
Dlhá metóda
Dlhý zoznam parametrov*args
nahradenie objektom
fluent interfaces
def params(*args) puts args.class puts args.inspectend
params('a', {:hello => 2}, 42)#=> Array#=> ["a", {:hello=>2}, 2]
activerecord 2.xvs
activerecord 3.xVisit.first(:select => "happened_at", :order => "happened_at ASC").happened_at
Visit.select(:happened_at) .order("happened_at ASC").first.happened_at
Primitívna obsesiaclass Debt < ActiveRecord::Base composed_of :debit, :class_name => "Money", :allow_nil => true, :mapping => [%w(debit_amount amount), %w(debit_currency currency)]end
debt.debit # => Money
switch/caseTemplate polymorfia
<% subject.infos.each do |info| %> <%= render :partial => "#{info.class.to_s}_detail", :subject => subject %><% end %>
Dátová triedaSkinny controller, fat model
Dátová triedaclass SubjectController < ApplicationController def show if @subject.updated_at < Time.now - 2.weeks.ago @subject.refresh end endend
class Subject < ActiveRecord::Baseend
Dátová trieda 2class SubjectController < ApplicationController def show @subject.update_if_stale endend
class Subject < ActiveRecord::Base def update_if_stale refresh if updated_at < Time.now - 2.weeks.ago endend
class Person < Subject def update_if_stale # update vsetky firmy kde je subjekt endend
Komentáre# download new version of articledef perform(x) # load article from the url 'x' ...end
def download_article(url) ...end
SOLID Principles
Single responsibilityprinciple
Objekt by mal mať iba jednu zodpovednosť
observers
cache sweepers
moduly (include, extend)
Open/closed principleTriedy by mali byť otvorené pre rozširovanie,
ale uzavreté pre modifikáciu
v ruby tomu ťažko zabrániť
Liskov substitution principleKaždý nadtyp by mal byť nahraditeľný
podtypom
kruh/kružnica
čo s polomerom?
Interface segregationprinciple
Hierarchia v rámci rozhraní - radšej viacšpecifických rozhraní ako jedno obrovské
v ruby nemá zmysel
Dependency inversionprinciple
Závislosť musí byť na rozhraní, nie nakonkrétnej implementácii
dependency injection
Domain driven designambiguous language
Array#first, Array#second, Array#forty_two
class Subject < ActiveRecord::Base has_many :debts
def is_suspicious? debts.any? or in_liquidation? endend
ZáverWith great power comes great responsibility
(Spidermanov otec)