Postobjektové programovanie v Ruby

3,224 views

Published on

Prezentácia z Rubyslavy #4 (@tkramar, @jsuchal)

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,224
On SlideShare
0
From Embeds
0
Number of Embeds
189
Actions
Shares
0
Downloads
13
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Postobjektové programovanie v Ruby

  1. 1. Postobjektovéprogramovanie v Ruby @tkramar, @jsuchal
  2. 2. Paradigmamodel, súbor princípov, spôsob uchopenia problému
  3. 3. 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
  4. 4. Objekt identita stavsprávanie
  5. 5. class Person @name = def initialize(name) @name = name end def say "Ahoj, ja som #{@name}" endend
  6. 6. Person Stav@name Správaniedef say "Ahoj, ja som #{@name}"end Identitap = Person.new(Janko)o.object_id
  7. 7. Identita sú dva objekty rovnaké? ==sú dva objekty rovnaké a rovnakého typu? eql? sú dva objekty totožné? equals?
  8. 8. Identitap1 = Person.new(Janko)#=> #<Person:0x00000001b5c100 @name="Janko">p2 = Person.new(Janko)#=> #<Person:0x00000001b556e8 @name="Janko">family = []family << p1family << p2family.uniq#=> [#<Person:0x00000001b5c100 @name="Janko">, #<Person:0x00000001b556e8 @name="Janko">]
  9. 9. class Person def ==(other) other.name == @name end def eql?(other) self == other endend
  10. 10. Identitap1 = Person.new(Janko)#=> #<Person:0x00000001b5c100 @name="Janko">p2 = Person.new(Janko)#=> #<Person:0x00000001b556e8 @name="Janko">family = []family << p1family << p2family.uniq#=> [#<Person:0x00000001b5c100 @name="Janko">, #<Person:0x00000001b556e8 @name="Janko">]
  11. 11. class Person def hash @name.hash endend
  12. 12. Identitap1 = Person.new(Janko)#=> #<Person:0x00000001b5c100 @name="Janko">p2 = Person.new(Janko)#=> #<Person:0x00000001b556e8 @name="Janko">family = []family << p1family << p2family.uniq#=> [#<Person:0x00000001b5c100 @name="Janko">]
  13. 13. Základné koncepty inheritance (dedenie)method overloading (preťažovanie metód)method overriding (prekonávanie metód)
  14. 14. Dedenieclass Instrument def tune ... endendclass Violin < Instrument def play tune "Taa daa" endend
  15. 15. Overloading v ruby nefungujeclass Instrument def play "Ta da" end def play(octave) "Ta da #{octave}" endendi = Instrument.newi.play #=> ArgumentError: wrong number of arguments (0 for 1)i.play(c dur) #=> "Ta da c dur"
  16. 16. Overridingclass Instrument def play "Ta da" endendclass Violin < Instrument def play "Tiii diii tiii" endendv = Violin.newv.play #=> "Tiii diii tiii"
  17. 17. Polymorfiaclass Instrument def play endendclass Violin < Instrument def play "Taa daa" endendclass Drum < Instrument def play "Dum dum" endendorchestra = [Violin.new, Violin.new, Drum.new]orchestra.each { |i| i.play }#=> "Taa daa", "Taa daa", "Dum dum"
  18. 18. Všetko v Ruby je Objekt
  19. 19. Aj nilObject.class #=> Classnil.class #=> NilClass
  20. 20. ActiveSupport tryp = Person.find(params[:id])p.address.downcase#=> NoMethodError: undefined method `downcase# for nil:NilClassp.address.try :downcase #=> nil
  21. 21. Aj metódaclass Person def greet(other_person) "Ahoj #{other_person}" endendm = Person.instance_method(:greet)m.class #=> UnboundMethodm.arity #=> 1m.name #=> :greetm.parameters #=> [[:req, :other_person]]
  22. 22. Aj triedaClass.class #=> Class .. o metaprogramovaní inokedy
  23. 23. Modulydef Class < Module
  24. 24. Modul ako namespacemodule Rubyslava class OOP endendmodule Pyvo class OOP endendRubyslava::OOP == Pyvo::OOP #=> false
  25. 25. Modul ako nositeľ kódumodule Rubyslava def hello "Rubyslava" endend
  26. 26. Mixinyclass RubyslavaAPyvo include Rubyslavaendrp = RubyslavaAPyvo.newrp.hello #=> "Rubyslava"class RubyslavaAPyvo extend RubyslavaendRubyslavaAPyvo.hello #=> "Rubyslava"
  27. 27. Viacnásobné dedenieclass RubyslavaAPyvo < Array include Rubyslava, PyvoendRubyslavaAPyvo.ancestors#=> [RubyslavaAPyvo, Rubyslava, Pyvo# Array, Object, Kernel, BasicObject]
  28. 28. Viditeľnosť v moduloch moduly zdieľajú všetko inštančné premenné (@) metódy
  29. 29. module AdditionalBehaviour def show greet endendclass Test include AdditionalBehaviour def greet "hello" endendTest.new.show #=> "hello"
  30. 30. module GreetingBehavior def greet "hello" endendmodule RelyOnGreetingBehavior def show greet endendclass Test include GreetingBehavior, RelyOnGreetingBehaviorendTest.new.show #=> "hello"
  31. 31. Zo života Enumerable acts_as_..ActiveSupport::Concern
  32. 32. class Tree include Enumerable def each(&block) leafs.each do { |n| yield n } endendt = Tree.newt.mapt.injectt.sum...
  33. 33. class Post acts_as_taggableendp = Post.find(1)p.tag_list #=> ["Ruby", "OOP"]
  34. 34. 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 endendclass Project < ActiveRecord::Base include Annotatable, Importable, Versionableend
  35. 35. Idiómyjazykovo špecifické vzory
  36. 36. Ruby idiómy bloky a, b = b,a a, b, c = array(cachovanie) @var ||= ..
  37. 37. Blokytransaction do |transaction| transaction.rollback if error?endDir.chdir(/) doendwith_disabled_keys doendwith_nested_loop_plan doend
  38. 38. VzoryVšeobecné riešenie často sa opakujúcehoproblému. Nie je to knižnica, ani kód, skôr šablóna.
  39. 39. "When I see patterns inmy programs, I consider it a sign of trouble." (Paul Graham)
  40. 40. Vzory, ktoré v Rubynetreba programovať
  41. 41. SingletonProblém: Zabezpečiť jednotný prístup k zdrojom (napr. databáza)Zlo, česť výnimkám (ActiveSupport::Inflections)
  42. 42. 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 :newendLogger.instance.log(message 1)
  43. 43. Singleton (lepšie)require singletonclass Logger include Singleton def initialize @log = File.open("log.txt", "a") end def log(msg) @log.puts(msg) endendLogger.instance.log(message 2)
  44. 44. IteratorProblém: Umožniť manipuláciu s kompozitnýmobjektom bez odhalenia jeho internej štruktúry
  45. 45. Iteratorclass Tree include Enumerable def each(&block) leafs.each { |e| yield e } endend
  46. 46. DecoratorProblém: pridávanie funkcionality do triedy počas behu programuDekorátor obalí pôvodnú triedu a kde treba rozšíri správanie
  47. 47. class StringDecorator < String def starts_with? substr # ... endendtitle = StringDecorator.new("Rubyslava")
  48. 48. Decorator = open classesclass String def starts_with? substr # ... endend"aye aye captain".starts_with? "pirate"#=> false
  49. 49. DelegatorProblém: Skrytie vnútorných závislostí
  50. 50. Delegatorinclude forwardableclass Car extend Forwardable def_delegators :@car_computer, :velocity, :distance def initialize @car_computer = CarComputer.new endendc = Car.newc.velocityc.distance
  51. 51. ProxyProblém: Skrytie implementačných detailov načítavania zdroja
  52. 52. Proxyrequire delegateclass Future < SimpleDelegator def initialize(&block) @_thread = Thread.start(&block) end def __getobj__ __setobj__(@_thread.value) if @_thread.alive? super endendgoogle = 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)).bodyendputs googleputs yahoo
  53. 53. Ďalšie užitočné vzory
  54. 54. Template methodProblém: Často sa opakujúci boilerplate kód. Kostra programu, algoritmu.
  55. 55. class Game def play prepare_board initialize_score while not end_of_game? make_move end endendclass Carcassonne < Game def make_move tile = tiles.pick_random ... endendclass Agricola < Game def make_move peasant = select_peasant ... endend
  56. 56. CompositeProblém: V stromových štruktúrachzamaskovať implementačný detail: uzly a listy.
  57. 57. class Person def say "#{@name}" endendclass Family attr_accessor :members def initialize(members) @members = members end def say @members.each { |m| m.say } endendf = Family.new(Person.new(Janko), Person.new(Tomas))t = Family.new(Person.new(Ferko), f)t.say #=> "Ferko", "Janko", "Tomas"
  58. 58. StrategyProblém: Výber vhodného algoritmu za behu programu
  59. 59. class FileLogger < Logger def log(message) File.open(a) { |f| f << message } endendclass DatabaseLogger < Logger def log(message) Log.create(:message => message) endend
  60. 60. StateProblém: Zmena správania objektu v rôznych stavoch
  61. 61. 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
  62. 62. light = TrafficLight.newlight.color # => "green"light.cycle!light.color # => "yellow"light.cycle!light.color # => "red"light.cycle!light.color # => "green"
  63. 63. ObserverProblém: Oddelenie core funkcionality a špecifického správania v určitých stavoch objektu.
  64. 64. class Comment < ActiveRecord::Baseendclass CommentObserver < ActiveRecord::Observer def after_save(comment) Notifications.deliver_comment("admin@do.com", "New comment was posted", comment) endend
  65. 65. Zápachy v kóde (Bad smells)
  66. 66. Vytváranie vlastného typového systému is_a?, kind_of? lepšie je respond_to?
  67. 67. class Container def add(node) if node.kind_of? Array node.each { |n| @nodes << n } else @nodes << node end endendcontainer.add(Tree.new(a, b)) #=> ???
  68. 68. class Container def add(node) unless node.respond_to? :each node = [node] end node.each { |n| @nodes << n } endendcontainer.add(Tree.new(a, b)) #=> [a, b]
  69. 69. Protipríklad ActiveRecord::sanitize_sql_for_assignmentdef sanitize_sql_for_assignment(assignments) case assignments when Array sanitize_sql_array(assignments) when Hash sanitize_sql_hash_for_assignment(assignments) else assignments endend
  70. 70. Visitor + typydef visit object method = "visit_#{object.class.name}" send method, object endmodule 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
  71. 71. Shotgun surgeryak niečo mením tak musím na viacerých miestach crosscutting concerns AOP (AspectJ)
  72. 72. AspectJpointcut writableHeaderMethods() : execution(* (WritableRequestHeader|| WritableResponseHeader) .*(..));before(HeaderWrapper header) : writableHeaderMethods() && this(header) { ...}
  73. 73. ActiveRecord::ObserverActiveController::Caching::Sweeper before/after/around filtre
  74. 74. Cachingclass DocumentController < ApplicationController caches_page :show caches_action :index def show; end def index; endend
  75. 75. 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
  76. 76. 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
  77. 77. Ťažko testovateľná trieda skúsiť si najprv napísať test rcov
  78. 78. Duplicitný kód reek metric_fu rubymine refaktoring vzor Template
  79. 79. Často sa meniaci kód churn
  80. 80. Dlhá metóda
  81. 81. Dlhý zoznam parametrov *args nahradenie objektom fluent interfaces
  82. 82. def params(*args) puts args.class puts args.inspectendparams(a, {:hello => 2}, 42)#=> Array#=> ["a", {:hello=>2}, 2]
  83. 83. activerecord 2.x vs activerecord 3.xVisit.first(:select => "happened_at", :order => "happened_at ASC").happened_atVisit.select(:happened_at) .order("happened_at ASC").first.happened_at
  84. 84. Primitívna obsesiaclass Debt < ActiveRecord::Base composed_of :debit, :class_name => "Money", :allow_nil => true, :mapping => [%w(debit_amount amount), %w(debit_currency currency)]enddebt.debit # => Money
  85. 85. switch/case Template polymorfia<% subject.infos.each do |info| %> <%= render :partial => "#{info.class.to_s}_detail", :subject => subject %><% end %>
  86. 86. Dátová triedaSkinny controller, fat model
  87. 87. Dátová triedaclass SubjectController < ApplicationController def show if @subject.updated_at < Time.now - 2.weeks.ago @subject.refresh end endendclass Subject < ActiveRecord::Baseend
  88. 88. Dátová trieda 2class SubjectController < ApplicationController def show @subject.update_if_stale endendclass Subject < ActiveRecord::Base def update_if_stale refresh if updated_at < Time.now - 2.weeks.ago endendclass Person < Subject def update_if_stale # update vsetky firmy kde je subjekt endend
  89. 89. Komentáre# download new version of articledef perform(x) # load article from the url x ...enddef download_article(url) ...end
  90. 90. SOLID Principles
  91. 91. Single responsibility principleObjekt by mal mať iba jednu zodpovednosť observers cache sweepers moduly (include, extend)
  92. 92. Open/closed principleTriedy by mali byť otvorené pre rozširovanie, ale uzavreté pre modifikáciu v ruby tomu ťažko zabrániť
  93. 93. Liskov substitution principle Každý nadtyp by mal byť nahraditeľný podtypom kruh/kružnica čo s polomerom?
  94. 94. Interface segregation principle Hierarchia v rámci rozhraní - radšej viacšpecifických rozhraní ako jedno obrovské v ruby nemá zmysel
  95. 95. Dependency inversion principleZávislosť musí byť na rozhraní, nie na konkrétnej implementácii dependency injection
  96. 96. Domain driven design ambiguous languageArray#first, Array#second, Array#forty_two
  97. 97. class Subject < ActiveRecord::Base has_many :debts def is_suspicious? debts.any? or in_liquidation? endend
  98. 98. ZáverWith great power comes great responsibility (Spidermanov otec)

×