postobjektové programovanie v ruby

Post on 10-May-2015

2.982 Views

Category:

Documents

4 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

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("admin@do.com", "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)

top related