98
Postobjektové programovanie v Ruby @tkramar, @jsuchal

Postobjektové programovanie v Ruby

Embed Size (px)

DESCRIPTION

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

Citation preview

Page 1: Postobjektové programovanie v Ruby

Postobjektovéprogramovanie v Ruby

@tkramar, @jsuchal

Page 2: Postobjektové programovanie v Ruby

Paradigmamodel, súbor princípov, spôsob uchopenia

problému

Page 3: Postobjektové programovanie v Ruby

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

Page 4: Postobjektové programovanie v Ruby

Objektidentita

stav

správanie

Page 5: Postobjektové programovanie v Ruby

class Person @name = ''

def initialize(name) @name = name end

def say "Ahoj, ja som #{@name}" endend

Page 6: Postobjektové programovanie v Ruby

PersonStav

@name

Správaniedef say "Ahoj, ja som #{@name}"end

Identitap = Person.new('Janko')o.object_id

Page 7: Postobjektové programovanie v Ruby

Identitasú dva objekty rovnaké?

==

sú dva objekty rovnaké a rovnakého typu?eql?

sú dva objekty totožné?equals?

Page 8: Postobjektové programovanie v Ruby

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">]

Page 9: Postobjektové programovanie v Ruby

class Person def ==(other) other.name == @name end

def eql?(other) self == other endend

Page 10: Postobjektové programovanie v Ruby

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">]

Page 11: Postobjektové programovanie v Ruby

class Person def hash @name.hash endend

Page 12: Postobjektové programovanie v Ruby

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">]

Page 13: Postobjektové programovanie v Ruby

Základné konceptyinheritance (dedenie)

method overloading (preťažovanie metód)

method overriding (prekonávanie metód)

Page 14: Postobjektové programovanie v Ruby

Dedenieclass Instrument def tune ... endend

class Violin < Instrument def play tune "Taa daa" endend

Page 15: Postobjektové programovanie v Ruby

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"

Page 16: Postobjektové programovanie v Ruby

Overridingclass Instrument def play "Ta da" endend

class Violin < Instrument def play "Tiii diii tiii" endend

v = Violin.newv.play #=> "Tiii diii tiii"

Page 17: Postobjektové programovanie v Ruby

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"

Page 18: Postobjektové programovanie v Ruby

Všetko v Ruby je Objekt

Page 19: Postobjektové programovanie v Ruby

Aj nilObject.class #=> Classnil.class #=> NilClass

Page 20: Postobjektové programovanie v Ruby

ActiveSupport tryp = Person.find(params[:id])

p.address.downcase #=> NoMethodError: undefined method `downcase' # for nil:NilClass

p.address.try :downcase #=> nil

Page 21: Postobjektové programovanie v Ruby

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]]

Page 22: Postobjektové programovanie v Ruby

Aj triedaClass.class #=> Class

.. o metaprogramovaní inokedy

Page 23: Postobjektové programovanie v Ruby

Modulydef Class < Module

Page 24: Postobjektové programovanie v Ruby

Modul ako namespacemodule Rubyslava class OOP endend

module Pyvo class OOP endend

Rubyslava::OOP == Pyvo::OOP #=> false

Page 25: Postobjektové programovanie v Ruby

Modul ako nositeľ kódumodule Rubyslava def hello "Rubyslava" endend

Page 26: Postobjektové programovanie v Ruby

Mixinyclass RubyslavaAPyvo include Rubyslavaend

rp = RubyslavaAPyvo.newrp.hello #=> "Rubyslava"

class RubyslavaAPyvo extend Rubyslavaend

RubyslavaAPyvo.hello #=> "Rubyslava"

Page 27: Postobjektové programovanie v Ruby

Viacnásobné dedenieclass RubyslavaAPyvo < Array include Rubyslava, Pyvoend

RubyslavaAPyvo.ancestors#=> [RubyslavaAPyvo, Rubyslava, Pyvo# Array, Object, Kernel, BasicObject]

Page 28: Postobjektové programovanie v Ruby

Viditeľnosť v modulochmoduly zdieľajú všetko

inštančné premenné (@)

metódy

Page 29: Postobjektové programovanie v Ruby

module AdditionalBehaviour def show greet endend

class Test include AdditionalBehaviour

def greet "hello" endend

Test.new.show #=> "hello"

Page 30: Postobjektové programovanie v Ruby

module GreetingBehavior def greet "hello" endend

module RelyOnGreetingBehavior def show greet endend

class Test include GreetingBehavior, RelyOnGreetingBehaviorend

Test.new.show #=> "hello"

Page 31: Postobjektové programovanie v Ruby

Zo životaEnumerable

acts_as_..

ActiveSupport::Concern

Page 32: Postobjektové programovanie v Ruby

class Tree include Enumerable

def each(&block) leafs.each do { |n| yield n } endend

t = Tree.newt.mapt.injectt.sum...

Page 33: Postobjektové programovanie v Ruby

class Post acts_as_taggableend

p = Post.find(1)p.tag_list #=> ["Ruby", "OOP"]

Page 34: Postobjektové programovanie v Ruby

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

Page 35: Postobjektové programovanie v Ruby

Idiómyjazykovo špecifické vzory

Page 36: Postobjektové programovanie v Ruby

Ruby idiómybloky

a, b = b,a

a, b, c = array

(cachovanie) @var ||= ..

Page 37: Postobjektové programovanie v Ruby

Blokytransaction do |transaction| transaction.rollback if error?end

Dir.chdir('/') doend

with_disabled_keys doend

with_nested_loop_plan doend

Page 38: Postobjektové programovanie v Ruby

VzoryVšeobecné riešenie často sa opakujúcehoproblému. Nie je to knižnica, ani kód, skôr

šablóna.

Page 39: Postobjektové programovanie v Ruby

"When I see patterns inmy programs, I consider it

a sign of trouble."(Paul Graham)

Page 40: Postobjektové programovanie v Ruby

Vzory, ktoré v Rubynetreba programovať

Page 41: Postobjektové programovanie v Ruby

SingletonProblém: Zabezpečiť jednotný prístup k zdrojom

(napr. databáza)Zlo, česť výnimkám (ActiveSupport::Inflections)

Page 42: Postobjektové programovanie v Ruby

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')

Page 43: Postobjektové programovanie v Ruby

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')

Page 44: Postobjektové programovanie v Ruby

IteratorProblém: Umožniť manipuláciu s kompozitnýmobjektom bez odhalenia jeho internej štruktúry

Page 45: Postobjektové programovanie v Ruby

Iteratorclass Tree include Enumerable

def each(&block) leafs.each { |e| yield e } endend

Page 46: Postobjektové programovanie v Ruby

DecoratorProblém: pridávanie funkcionality do triedy

počas behu programu

Dekorátor obalí pôvodnú triedu a kde trebarozšíri správanie

Page 47: Postobjektové programovanie v Ruby

class StringDecorator < String def starts_with? substr # ... endend

title = StringDecorator.new("Rubyslava")

Page 48: Postobjektové programovanie v Ruby

Decorator = open classesclass String def starts_with? substr # ... endend

"aye aye captain".starts_with? "pirate" #=> false

Page 49: Postobjektové programovanie v Ruby

DelegatorProblém: Skrytie vnútorných závislostí

Page 50: Postobjektové programovanie v Ruby

Delegatorinclude 'forwardable'class Car extend Forwardable

def_delegators :@car_computer, :velocity, :distance

def initialize @car_computer = CarComputer.new endend

c = Car.newc.velocityc.distance

Page 51: Postobjektové programovanie v Ruby

ProxyProblém: Skrytie implementačných detailov

načítavania zdroja

Page 52: Postobjektové programovanie v Ruby

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

Page 53: Postobjektové programovanie v Ruby

Ďalšie užitočné vzory

Page 54: Postobjektové programovanie v Ruby

Template methodProblém: Často sa opakujúci boilerplate kód.

Kostra programu, algoritmu.

Page 55: Postobjektové programovanie v Ruby

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

Page 56: Postobjektové programovanie v Ruby

CompositeProblém: V stromových štruktúrachzamaskovať implementačný detail:

uzly a listy.

Page 57: Postobjektové programovanie v Ruby

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"

Page 58: Postobjektové programovanie v Ruby

StrategyProblém: Výber vhodného algoritmu

za behu programu

Page 59: Postobjektové programovanie v Ruby

class FileLogger < Logger def log(message) File.open('a') { |f| f << message } endend

class DatabaseLogger < Logger def log(message) Log.create(:message => message) endend

Page 60: Postobjektové programovanie v Ruby

StateProblém: Zmena správania objektu

v rôznych stavoch

Page 61: Postobjektové programovanie v Ruby

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

Page 62: Postobjektové programovanie v Ruby

light = TrafficLight.newlight.color # => "green"light.cycle!light.color # => "yellow"light.cycle!light.color # => "red"light.cycle!light.color # => "green"

Page 63: Postobjektové programovanie v Ruby

ObserverProblém: Oddelenie core funkcionality

a špecifického správania v určitýchstavoch objektu.

Page 64: Postobjektové programovanie v Ruby

class Comment < ActiveRecord::Baseend

class CommentObserver < ActiveRecord::Observer def after_save(comment) Notifications.deliver_comment("[email protected]", "New comment was posted", comment) endend

Page 65: Postobjektové programovanie v Ruby

Zápachy v kóde(Bad smells)

Page 66: Postobjektové programovanie v Ruby

Vytváranie vlastnéhotypového systému

is_a?, kind_of?

lepšie je respond_to?

Page 67: Postobjektové programovanie v Ruby

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')) #=> ???

Page 68: Postobjektové programovanie v Ruby

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']

Page 69: Postobjektové programovanie v Ruby

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

Page 70: Postobjektové programovanie v Ruby

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

Page 71: Postobjektové programovanie v Ruby

Shotgun surgeryak niečo mením tak musím na viacerých

miestach

crosscutting concerns

AOP (AspectJ)

Page 72: Postobjektové programovanie v Ruby

AspectJpointcut writableHeaderMethods() : execution(* (WritableRequestHeader|| WritableResponseHeader) .*(..));

before(HeaderWrapper header) : writableHeaderMethods() && this(header) { ...}

Page 73: Postobjektové programovanie v Ruby

ActiveRecord::Observer

ActiveController::Caching::Sweeper

before/after/around filtre

Page 74: Postobjektové programovanie v Ruby

Cachingclass DocumentController < ApplicationController caches_page :show caches_action :index

def show; end

def index; endend

Page 75: Postobjektové programovanie v Ruby

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

Page 76: Postobjektové programovanie v Ruby

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

Page 77: Postobjektové programovanie v Ruby

Ťažko testovateľná triedaskúsiť si najprv napísať test

rcov

Page 78: Postobjektové programovanie v Ruby

Duplicitný kódreek

metric_fu

rubymine

refaktoring

vzor Template

Page 79: Postobjektové programovanie v Ruby

Často sa meniaci kódchurn

Page 80: Postobjektové programovanie v Ruby

Dlhá metóda

Page 81: Postobjektové programovanie v Ruby

Dlhý zoznam parametrov*args

nahradenie objektom

fluent interfaces

Page 82: Postobjektové programovanie v Ruby

def params(*args) puts args.class puts args.inspectend

params('a', {:hello => 2}, 42)#=> Array#=> ["a", {:hello=>2}, 2]

Page 83: Postobjektové programovanie v Ruby

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

Page 84: Postobjektové programovanie v Ruby

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

Page 85: Postobjektové programovanie v Ruby

switch/caseTemplate polymorfia

<% subject.infos.each do |info| %> <%= render :partial => "#{info.class.to_s}_detail", :subject => subject %><% end %>

Page 86: Postobjektové programovanie v Ruby

Dátová triedaSkinny controller, fat model

Page 87: Postobjektové programovanie v Ruby

Dátová triedaclass SubjectController < ApplicationController def show if @subject.updated_at < Time.now - 2.weeks.ago @subject.refresh end endend

class Subject < ActiveRecord::Baseend

Page 88: Postobjektové programovanie v Ruby

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

Page 89: Postobjektové programovanie v Ruby

Komentáre# download new version of articledef perform(x) # load article from the url 'x' ...end

def download_article(url) ...end

Page 90: Postobjektové programovanie v Ruby

SOLID Principles

Page 91: Postobjektové programovanie v Ruby

Single responsibilityprinciple

Objekt by mal mať iba jednu zodpovednosť

observers

cache sweepers

moduly (include, extend)

Page 92: Postobjektové programovanie v Ruby

Open/closed principleTriedy by mali byť otvorené pre rozširovanie,

ale uzavreté pre modifikáciu

v ruby tomu ťažko zabrániť

Page 93: Postobjektové programovanie v Ruby

Liskov substitution principleKaždý nadtyp by mal byť nahraditeľný

podtypom

kruh/kružnica

čo s polomerom?

Page 94: Postobjektové programovanie v Ruby

Interface segregationprinciple

Hierarchia v rámci rozhraní - radšej viacšpecifických rozhraní ako jedno obrovské

v ruby nemá zmysel

Page 95: Postobjektové programovanie v Ruby

Dependency inversionprinciple

Závislosť musí byť na rozhraní, nie nakonkrétnej implementácii

dependency injection

Page 96: Postobjektové programovanie v Ruby

Domain driven designambiguous language

Array#first, Array#second, Array#forty_two

Page 97: Postobjektové programovanie v Ruby

class Subject < ActiveRecord::Base has_many :debts

def is_suspicious? debts.any? or in_liquidation? endend

Page 98: Postobjektové programovanie v Ruby

ZáverWith great power comes great responsibility

(Spidermanov otec)