Slide 1

Slide 1 text

Linguagens Dinâmicas São Melhores para Implementar OO Cássio Marques - RubyConf Brasil 2012

Slide 2

Slide 2 text

@cassiomarques http://cassiomarques.wordpress.com

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Avisos

Slide 5

Slide 5 text

Muita coisa pra mostrar

Slide 6

Slide 6 text

Vamos ter que correr!

Slide 7

Slide 7 text

Muito código!

Slide 8

Slide 8 text

Não só para Rubistas :)

Slide 9

Slide 9 text

Por que usamos linguagens orientadas a objetos?

Slide 10

Slide 10 text

Maior nível de abstração

Slide 11

Slide 11 text

Facilidade para modelar o domínio do problema

Slide 12

Slide 12 text

Estado + Comportamento = Objetos

Slide 13

Slide 13 text

Encapsulamento

Slide 14

Slide 14 text

Melhor organização do código

Slide 15

Slide 15 text

Facilidade para evoluir o código em iterações

Slide 16

Slide 16 text

Separação de responsabilidades

Slide 17

Slide 17 text

História

Slide 18

Slide 18 text

Simula 67 Kristen Nygaard and Ole-Johan Dahl - Noruega (Tipagem estática)

Slide 19

Slide 19 text

Smalltalk Alan Kay - 70’s (Tipagem dinâmica)

Slide 20

Slide 20 text

C++ Bjarne Stroustrup - 1979 (Tipagem estática)

Slide 21

Slide 21 text

Java James Gosling - 1995 (Tipagem estática)

Slide 22

Slide 22 text

Ruby Yukihiro Matsumoto - 1995 (Tipagem dinâmica)

Slide 23

Slide 23 text

Herança

Slide 24

Slide 24 text

C++ permite herança múltipla

Slide 25

Slide 25 text

class FlyingAnimal { public: void fly() { cout << "Flying..." << endl; } };

Slide 26

Slide 26 text

class Feline { public: void talk() { cout << "Meow!" << endl; } };

Slide 27

Slide 27 text

class FlyingCat : public FlyingAnimal, public Feline { };

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

Java não permite herança múltipla

Slide 30

Slide 30 text

package br.com.rubyconf; public class FlyingAnimal { public void fly() { System.out.println("Flying..."); } }

Slide 31

Slide 31 text

package br.com.rubyconf; public class Feline { public void talk() { System.out.println("Meow!"); } }

Slide 32

Slide 32 text

package br.com.rubyconf; // Não funciona! public class FlyingCat extends FlyingAnimal, Feline { }

Slide 33

Slide 33 text

Java Interfaces

Slide 34

Slide 34 text

package br.com.rubyconf; public interface Feline { public void talk(); }

Slide 35

Slide 35 text

package br.com.rubyconf; public interface FlyingAnimal { public void fly(); }

Slide 36

Slide 36 text

package br.com.rubyconf; public class FlyingCat implements FlyingAnimal,Feline { public void talk() { // implementation... } public void fly() { // implementation... } }

Slide 37

Slide 37 text

Cria-se um contrato formal

Slide 38

Slide 38 text

Eu quero ter uma implementação padrão!!!

Slide 39

Slide 39 text

Porque herança múltipla é ruim?

Slide 40

Slide 40 text

class A { public: void foo() { cout << "calling A's implementation" << endl; }; };

Slide 41

Slide 41 text

class B : public A { public: void foo() { cout << "calling B's implementation" << endl; } }; class C : public A { public: void foo() { cout << "calling C's implementation" << endl; } };

Slide 42

Slide 42 text

class D : public B, public C { }; int main (int argc, char const* argv[]) { D d = D(); d.foo(); return 0; }

Slide 43

Slide 43 text

teste.cpp|50 error| error: request for member ‘foo’ is ambiguous teste.cpp|26 error| error: candidates are: void A::foo() teste.cpp|39 error| error: void C::foo() teste.cpp|33 error| error: void B::foo()

Slide 44

Slide 44 text

Ruby

Slide 45

Slide 45 text

Também não tem herança múltipla

Slide 46

Slide 46 text

Mas temos modules

Slide 47

Slide 47 text

Mixins

Slide 48

Slide 48 text

module Sellable def price 15.00 end end module Discountable def discount 0.1 end end

Slide 49

Slide 49 text

class Product include Sellable include Discountable def price super - super * discount end end item = Product.new puts item.price # 13.5

Slide 50

Slide 50 text

ou em tempo de execução...

Slide 51

Slide 51 text

class Product end product = Product.new puts product.price # undefined method `price' for # (NoMethodError)

Slide 52

Slide 52 text

Product.instance_eval { include Sellable } puts product.price # 15.0

Slide 53

Slide 53 text

module Discountable def discount 0.1 end def price_with_discount price - price * discount end end

Slide 54

Slide 54 text

product.extend Discountable puts product.price_with_discount # 13.5

Slide 55

Slide 55 text

ou

Slide 56

Slide 56 text

Product.instance_eval { include Discountable } puts product.price_with_discount # 13.5

Slide 57

Slide 57 text

Mas como isso funciona?

Slide 58

Slide 58 text

Object model do Ruby

Slide 59

Slide 59 text

class BaseClass # ... end class OurClass < BaseClass # ... end some_object = OurClass.new some_object.do_something

Slide 60

Slide 60 text

some_object.do_something Singleton class OurClass included modules included modules BaseClass Object (nativo) Module Kernel (nativo) BasicObject (nativo) undefined method `do_something' for # (NoMethodError)

Slide 61

Slide 61 text

Podemos incluir quantos “mixins” quisermos

Slide 62

Slide 62 text

Sempre saberemos qual versão do método será executada

Slide 63

Slide 63 text

Podemos adicionar comportamento em tempo de execução

Slide 64

Slide 64 text

Classes abstratas

Slide 65

Slide 65 text

C++

Slide 66

Slide 66 text

class Vehicle { public: void virtual move() = 0; void setColor(string _color) { color = _color; } private: string color; };

Slide 67

Slide 67 text

class Car : public Vehicle { public: void move() { cout << "Moving..." << endl; } };

Slide 68

Slide 68 text

Java

Slide 69

Slide 69 text

package br.com.rubyconf; public abstract class Vehicle { private String color; public abstract void move(); public void setColor(String color) { this.color = color; } }

Slide 70

Slide 70 text

package br.com.rubyconf; public class Car extends Vehicle { public void move() { System.out.println("Moving..."); } }

Slide 71

Slide 71 text

Ruby

Slide 72

Slide 72 text

class Vehicle def move raise NotImplementedError end end

Slide 73

Slide 73 text

class Car < Vehicle def move puts "Moving..." end end

Slide 74

Slide 74 text

Não precisamos de classes abstratas em Ruby

Slide 75

Slide 75 text

Em Ruby não há como definir um contrato formal

Slide 76

Slide 76 text

O comportamento de um tipo pode mudar em tempo de execução

Slide 77

Slide 77 text

Exemplo: Enumerable

Slide 78

Slide 78 text

Faz com que um objeto se comporte como uma coleção de objetos

Slide 79

Slide 79 text

Contrato informal: é preciso que a classe que inclui o module Enumerable defina um método each

Slide 80

Slide 80 text

class Book attr_accessor :title def initialize(title) self.title = title end end

Slide 81

Slide 81 text

class Library include Enumerable def initialize @books = [] end def add_book(book) @books << book end def each @books.each { |book| yield book } end end

Slide 82

Slide 82 text

library = Library.new library.add_book Book.new("Book 1") library.add_book Book.new("Book 2") library.add_book Book.new("Book 3") library.each { |book| puts book.title } # Book 1 # Book 2 # Book 3

Slide 83

Slide 83 text

Polimorfismo

Slide 84

Slide 84 text

C++

Slide 85

Slide 85 text

class Tax { public: float virtual dueValue() = 0; float virtual retainedValue() = 0; string virtual getName() = 0; Tax(float _value) {} protected: float value; };

Slide 86

Slide 86 text

class Irpj : public Tax { public: Irpj(float _value) : Tax(_value) { this->value = _value; } float dueValue() { return value * 0.033; } float retainedValue() { return value * 0.015; } string getName() { return "IRPJ"; } };

Slide 87

Slide 87 text

class Pis : public Tax { public: Pis(float _value) : Tax(_value) { this->value = _value; } float dueValue() { return 0.0; } float retainedValue() { return value * 0.0065; } string getName() { return "PIS"; } };

Slide 88

Slide 88 text

class Invoice { public: Invoice(float _value) { value = _value; Irpj *irpj = new Irpj(value); Pis *pis = new Pis(value); taxes.push_back(irpj); taxes.push_back(pis); } void print() { for (unsigned int i = 0; i < taxes.size(); i++) { Tax *tax = taxes[i]; cout << tax->getName() << endl; cout << "Devido: R$" << tax->dueValue() << endl; cout << "Retido: R$" << tax->retainedValue() << endl; } } private: float value; vector taxes; };

Slide 89

Slide 89 text

int main (int argc, char const* argv[]) { Invoice invoice = Invoice(1000.00); invoice.print(); return 0; } // IRPJ // Devido: R$33 // Retido: R$15 // PIS // Devido: R$0 // Retido: R$6.5

Slide 90

Slide 90 text

Java

Slide 91

Slide 91 text

package br.com.rubyconf; public interface Tax { double dueValue(); double retainedValue(); String getName(); }

Slide 92

Slide 92 text

public class Irpj implements Tax { private double value; public Irpj(double value) { this.value = value; } public double dueValue() { return value * 0.033; } public double retainedValue() { return value * 0.015; } public String getName() { return "IRPJ"; } }

Slide 93

Slide 93 text

public class Pis implements Tax { private double value; public Pis(double value) { this.value = value; } public double dueValue() { return 0.0; } public double retainedValue() { return value * 0.0065; } public String getName() { return "PIS"; } }

Slide 94

Slide 94 text

public class Invoice { private List taxes = new ArrayList(); private double value; public Invoice(double value) { this.value = value; Irpj irpj = new Irpj(this.value); Pis pis = new Pis(this.value); taxes.add(irpj); taxes.add(pis); } public void print() { for(Tax tax : taxes) { System.out.println(tax.getName()); System.out.println("Devido: R$" + tax.dueValue()); System.out.println("Retido: R$" + tax.retainedValue()); } } }

Slide 95

Slide 95 text

package br.com.rubyconf; public class Main { public static void main(String[] args) { Invoice invoice = new Invoice(1000.00); invoice.print(); } } //IRPJ //Devido: R$33.0 //Retido: R$15.0 //PIS //Devido: R$0.0 //Retido: R$6.5

Slide 96

Slide 96 text

Ruby

Slide 97

Slide 97 text

module Tax attr_reader :value def initialize(value) @value = value end end

Slide 98

Slide 98 text

class Irpj include Tax def name "IRPJ" end def retained_value value * 0.015 end def due_value value * 0.033 end end

Slide 99

Slide 99 text

class Pis include Tax def name "PIS" end def retained_value value * 0.0065 end def due_value 0.0 end end

Slide 100

Slide 100 text

class Invoice def initialize(value) @value = value @taxes = [] @taxes << Irpj.new(value) @taxes << Pis.new(value) end def print @taxes.each do |tax| puts tax.name puts "Devido: R$#{tax.due_value}" puts "Retido: R$#{tax.retained_value}" end end end

Slide 101

Slide 101 text

invoice = Invoice.new(1000.0) invoice.print # IRPJ # Devido: R$33.0 # Retido: R$15.0 # PIS # Devido: R$0.0 # Retido: R$6.5

Slide 102

Slide 102 text

Sobrecarga de métodos

Slide 103

Slide 103 text

C++

Slide 104

Slide 104 text

class Printer { public: void print(Report report) { // ... } void print(Note note) { // ... } void print(Document document) { // ... } };

Slide 105

Slide 105 text

Java

Slide 106

Slide 106 text

public class Printer { public void print(Document document) { // ... } public void print(Note note) { // ... } public void print(Report report) { // ... } }

Slide 107

Slide 107 text

Ruby

Slide 108

Slide 108 text

class Printer def print(printable) # ... end end

Slide 109

Slide 109 text

E se a implementação para cada tipo de “printable” for diferente?

Slide 110

Slide 110 text

class Printer def print(printable) case printable when Document print_document printable when Note print_note printable when Report print_report printable end end private def print_document(printable) # ... end def print_note(printable) # ... end def print_report(printable) # ... end end

Slide 111

Slide 111 text

Qual a vantagem?!

Slide 112

Slide 112 text

Somente uma forma de chamar o método

Slide 113

Slide 113 text

Implementação interna está encapsulada

Slide 114

Slide 114 text

Tipos genéricos

Slide 115

Slide 115 text

C++

Slide 116

Slide 116 text

template class Printer { public: void print(Printable printable) { cout << "Printing a " << typeid(printable).name() << endl; } };

Slide 117

Slide 117 text

int main (int argc, char const* argv[]) { Printer printer = Printer(); printer.print(Note()); return 0; }

Slide 118

Slide 118 text

Java

Slide 119

Slide 119 text

public class Printer { public void print(Printable document) { System.out.println("Printing a " + document); } }

Slide 120

Slide 120 text

public class Main { public static void main(String[] args) { Printer printer = new Printer(); printer.print(new Document()); } }

Slide 121

Slide 121 text

Ruby

Slide 122

Slide 122 text

class Printer def print(printable) puts "Printing a #{printable.class.name}..." end end

Slide 123

Slide 123 text

Coleções genéricas

Slide 124

Slide 124 text

C++

Slide 125

Slide 125 text

template class Printer { public: Printer() { printables = vector(); } void print(Printable printable) { cout << "Printing a " << typeid(printable).name() << endl; } void addPrintable(Printable printable) { printables.push_back(printable); } void printAll() { for (int i = 0; i < printables.size(); i++) { print(printables[i]); } } private: vector printables; };

Slide 126

Slide 126 text

Java

Slide 127

Slide 127 text

public class Printer { private List printables = new ArrayList(); public void addPrintable(Printable printable) { printables.add(printable); } public void print(Printable document) { System.out.println("Printing a " + document); } public void printAll() { for(Printable printable : printables) { print(printable); } } }

Slide 128

Slide 128 text

public class Main { public static void main(String[] args) { Printer printer = new Printer(); Document doc1 = new Document(); Document doc2 = new Document(); printer.addPrintable(doc1); printer.addPrintable(doc2); printer.printAll(); } }

Slide 129

Slide 129 text

Ruby

Slide 130

Slide 130 text

class Printer def initialize @printables = [] end def add_printable(printable) @printables << printable end def print(printable) puts "Printing a #{printable.class.name}..." end def print_all @printables.each { |p| print p } end end

Slide 131

Slide 131 text

printer = Printer.new doc1 = Document.new printer.add_printable doc1 doc2 = Document.new printer.add_printable doc2 printer.print_all

Slide 132

Slide 132 text

e ainda...

Slide 133

Slide 133 text

printer = Printer.new doc = Document.new printer.add_printable doc1 note = Note.new printer.add_printable note report = Report.new printer.add_printable report foo = Foobar.new printer.add_printable foo printer.print_all

Slide 134

Slide 134 text

Desde que instâncias de FooBar respondam aos métodos necessários para serem “impressos”, vai funcionar :)

Slide 135

Slide 135 text

Testes

Slide 136

Slide 136 text

Mocks e stubs

Slide 137

Slide 137 text

Adicionar itens a um carrinho e calcular o valor total

Slide 138

Slide 138 text

C++

Slide 139

Slide 139 text

class TotalCalculatorInterface { public: virtual ~TotalCalculatorInterface() = 0; virtual double total() = 0; };

Slide 140

Slide 140 text

class TotalCalculatorWithDiscount : public TotalCalculator { private: double discountPercentage; public: TotalCalculatorWithDiscount(double discountPercentage) { this->discountPercentage = discountPercentage; } double total(vector items) { double t = 0.0; for (unsigned int i = 0; i < items.size(); i++) { t += items[i].getTotal(); } return t - (t * discountPercentage/100); } };

Slide 141

Slide 141 text

class Cart { private: vector items; TotalCalculator *calculator; public: Cart(TotalCalculator *calculator) { this->calculator = calculator; } vector getItems() { return items; } double getTotal(); }; double Cart::getTotal() { return calculator->total(); }

Slide 142

Slide 142 text

class MockTotalCalculator : public TotalCalculator { public: virtual ~MockTotalCalculator() { } MOCK_METHOD1(total, double(vector items)); };

Slide 143

Slide 143 text

TEST(Cart, ReturnsTotalValue) { MockTotalCalculator calculator; LineItem lineItem1 = LineItem(10.0, 2); LineItem lineItem2 = LineItem(15.0, 3); Cart cart = Cart(calculator); cart.addItem(lineItem1); cart.addItem(lineItem2); vector items; items.push_back(line_item1); items.push_back(line_item2); EXPECT_CALL(calculator, total(items)) .WillOnce(Return(65.00)); EXPECT_EQ(cart.getTotal(), 65.00); }

Slide 144

Slide 144 text

Java

Slide 145

Slide 145 text

public interface TotalCalculator { public double total(List items); }

Slide 146

Slide 146 text

public class TotalCalculatorWithDiscount implements TotalCalculator { private double percentageDiscount; public TotalCalculatorWithDiscount(double percentageDiscount) { this.percentageDiscount = percentageDiscount; } public double total(List items) { double t = 0.0; for(LineItem item : items) { t += item.getTotal(); } return t - (t * percentageDiscount/100.0); } }

Slide 147

Slide 147 text

public class Cart { private List items = new ArrayList(); private TotalCalculator calculator; public Cart(TotalCalculator calculator) { this.calculator = calculator; } public void addItem(LineItem item) { items.add(item); } public double getTotal() { return calculator.total(items); } }

Slide 148

Slide 148 text

import static org.mockito.Mockito.*; public class TestCart { @Test public void calculatesTotalValue() { LineItem item1 = mock(LineItem.class); LineItem item2 = mock(LineItem.class); List items = new ArrayList(); TotalCalculator mockedCalculator = mock(TotalCalculator.class); items.add(item1); items.add(item2); when(mockedCalculator.total(items)).thenReturn(65.00); Cart cart = new Cart(mockedCalculator); cart.addItem(item1); cart.addItem(item2); assertEquals(cart.getTotal(), 65.00, 0.0001); verify(mockedCalculator).total(items); } }

Slide 149

Slide 149 text

Ruby

Slide 150

Slide 150 text

class TotalCalculatorWithDiscount def initialize(percentage_discount) @percentage_discount = percentage_discount end def total(items) t = items.inject(0.0) { |total, item| total += item.total } t - (t * percentage_discount / 100.0) end end

Slide 151

Slide 151 text

class Cart attr_reader :items def initialize(calculator) @items = [] @calculator = calculator end def add_item(item) @items << item end def total @calculator.total items end end

Slide 152

Slide 152 text

describe Cart do describe "#total" do it "calculates the cart's total" do calculator = stub item1 = stub item2 = stub calculator.should_receive(:total) .with([item1, item2]).and_return(65.00) cart = Cart.new calculator cart.add_item item1 cart.add_item item2 cart.total.should == 65.00 end end end

Slide 153

Slide 153 text

CONCLUSÕES

Slide 154

Slide 154 text

Em linguagens estáticas, o importante é saber o tipo de um objeto

Slide 155

Slide 155 text

Se você sabe o tipo, você sabe a quais contratos o objeto atende (quais métodos são implementados)

Slide 156

Slide 156 text

Em Ruby, o importante é o que um objeto sabe fazer

Slide 157

Slide 157 text

As regras de negócio já são suficientemente complexas

Slide 158

Slide 158 text

Não precisamos da complexidade da linguagem

Slide 159

Slide 159 text

Tipagem estática só garante que você vai fornecer os tipos corretos

Slide 160

Slide 160 text

Você não está protegido contra erros de lógica :)

Slide 161

Slide 161 text

Esse tipo de erro você pode eliminar escrevendo testes automatizados

Slide 162

Slide 162 text

Você deveria estar fazendo isso independentemente da tipagem da linguagem

Slide 163

Slide 163 text

Código mais claro é melhor do que código mais rápido (na maioria das vezes)

Slide 164

Slide 164 text

?

Slide 165

Slide 165 text

Obrigado :)