Programação Orientada aos Objectos Set/2005 Uma Introdução Usando C++ Paulo Marques Departamento de Eng. Informática Universidade de Coimbra [email protected] Sobre o que é que vamos falar? Primeira parte... Linguagens de programação Conceitos de programação orientadas aos objectos Representação visual dos mesmos (UML) Como é que se exprimem em C++ Segunda parte... “Network Simulator”, uma aplicação OOP 2 » If you think C++ is not overly complicated, just what is a protected abstract virtual base pure virtual private destructor and when was the last time you needed one? « Tom Cargill » C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do, it blows away your whole leg. « Bjarne Stroustrup 3 Paradigmas de Programação Actualmente existem quatro paradigmas de linguagens de programação em uso comum: Imperativas (e.g. C, Pascal, Fortran) Funcionais (e.g. LISP, Scheme) Lógicas/Declarativas (e.g. Prolog) Orientadas-aos-Objectos (e.g. Java, C++, C#, Smalltalk) Hoje em dia a indústria é dominada pelos paradigmas Imperativo e Orientado-aos-Objectos 4 Linguagens Imperativas Para programar um computador diz-se que… PROGRAMA = ESTRUTURAS DADOS + ALGORITMOS No programa existem variáveis que representam os dados Existe um conjunto de instruções que sucessivamente, a cada instrução, altera o valor das variáveis, manipulando os dados Segue de forma bastante próxima o modelo básico de funcionamento do processador Exemplos: C, Pascal, Fortran 5 Programação Orientada aos Objectos Os grandes problema da programação imperativa, estruturada: Grande Acoplamento! Baixa Coesão! f() g() h() f()f() f()f() m() f() j() f()p() l() f() f() k() Estruturas de Dados Temos os dados, e o programa é constituído por milhares de funções que… -- Ou manipulam directamente esses dados -- Ou trocam imensos valores por parâmetro 6 Programação Orientada aos Objectos Em OOP (Object-Oriented Programming), as funções estão encapsuladas juntamente com os dados a que podem (e devem aceder) l() f() Dados Dados m() g() q() h() f() Dados g() h() 7 Programação Orientada aos Objectos A principal ideia das objectos é que: Programação Imperativa Procedimental: Apenas as funções relacionadas com os dados lhes podem aceder Reduzir o acoplamento e aumentar a coesão Isto é, permitir a construção de software em projectos de larga escala, de forma consistente e fácil de gerir Para além disso, é muito mais natural pensar em termos de objectos e suas relações do que em termos de dados e algoritmos PROGRAMA = DADOS + ALGORITMOS Programação Orientada aos Objectos PROGRAMA = OBJECTOS + RELAÇÕES 8 Programação OOP e o C++ Ole-Johan Dahl e Kristen Nygaard, Noruegueses, inventam a linguagem Simula-67, para simulação. A motivação foi que ao realizarem simulações sobre o mundo real, com centenas de entidades, tornava-se inviável especificar explicitamente todas as interacções possíveis. Introduziu o conceito de classe/encapsulamento, objecto e uma forma de herança Alan Kay, Americano, inventa a linguagem SmallTalk (circa 1972), considerada a primeira verdadeira linguagem OOP Tudo são objectos; Os objectos comunicam trocando mensagens. Fortemente associada ao GUI! ...inventou o conceito de computador pessoal, GUI e Portátil, numa altura em que tal era... RADICAL! “There is no reason anyone would want a computer in their home." (Ken Olsen, Digital Equipment Corp, 1977) 9 Programação OOP e o C++ Bell Labs, 1979, Bjarne Stroustrup queria ter classes e objectos na linguagem C Motivação: análise do Kernel do UNIX para possibilitar computação distribuída! ... OOP facilitaria o processo Criou um pré-processador que compilava a sua linguagem “C with Classes” para C Primeira linguagem com suporte OOP largamente utilizada na indústria 1983, a linguagem é renomeada C++. 1985: primeira versão comercial Dominou os anos 90. Última versão do standard: 2003 Devido à complexidade da linguagem, praticamente nenhum compilador implementa o standard completamente... 10 Fundamentos da OOP Noção de Classe e Objecto Encapsulamento Herança Polimorfismo 11 Preliminares... (Temos de garantir os básicos!) // Importa biblioteca e passa a usar o espaço de nomes “standard” #include <iostream> #include <vector> using namespace std; // Programa principal int main() { // Declara uma tabela de inteiros de tamanho variável vector<int> myTable; // Adiciona-lhe 10 números for (unsigned i=0; i<10; i++) myTable.push_back(i); // Imprime o seu conteúdo for (unsigned i=0; i<myTable.size(); i++) cout << myTable[i] << endl; return 0; } 12 O resultado... 13 Noção de Classe e Objecto Uma classe representa um grupo (ou tipo) de coisas Um objecto (ou instância) representa uma coisa em particular de um grupo. Exemplo: Pessoa, Automóvel É sempre um NOME Uma classe tem operações associadas: métodos Os métodos representam acções sobre uma entidade, logo são VERBOS Exemplo: “Paulo Marques”, “43-23-XM” Toda a programação OOP baseia-se em encontrar classes e relações entre classes! 14 Mais alguns exemplos Uma REDE possui NODOS. Cada NODO pode ser um COMPUTADOR ou um REPETIDOR Numa rede pode fazer-se um Broadcast Broadcast é um método! Um nodo pode enviar e receber PACOTEs Rede, Nodo, Computador e Repetidor são classes! Enviar e Receber são métodos! PACOTE é uma classe! Um LEITOR_DE_MP3 toca MUSICA! LEITOR e MUSICA são classes! Toca é um método! 15 Classe Pessoa Pessoa.h class Pessoa { private: string _nome; int _idade; Pessoa public: Pessoa(string nome, int idade); void imprime(); }; -_nome -_idade +Pessoa(in nome, in idade) +imprime() Basics.cpp #include "Pessoa.h“ int main() { Pessoa aluno("Carlos Manuel", 30); Pessoa professor("Guilherme To", 23); aluno.imprime(); professor.imprime(); return 0; } 16 Classe Pessoa Pessoa aluno("Carlos Manuel", 30); Pessoa professor("Guilherme To", 23); aluno.imprime(); professor.imprime(); 17 Classe Pessoa Pessoa aluno("Carlos Manuel", 30); Pessoa professor("Guilherme To", 23); aluno.imprime(); professor.imprime(); classe objectos (instâncias) A existência de “classes de coisas” e as suas “instâncias”, permitem-me manipular e simular o mundo de forma abstracta! A noção de encapsulamento – os dados estarem escondidos – é fundamental! 18 A implementação de Pessoa... Pessoa.cpp Pessoa::Pessoa(string nome, int idade) { _nome = nome; _idade = idade; } void Pessoa::imprime() { cout << "[" << _nome << "/" << _idade << "]" << endl; } 19 Herança É possível definir especializações de uma classe base. Chama-se a isso uma “classe derivada” Um COMPUTADOR é um NODO de rede. Um REPETIDOR é um NODO de rede. A classe derivada contém tudo o que a base contém, mas com informação/métodos adicionais Computador e Repetidor são tudo o que um Nodo de rede é, mas tendo funcionalidade específica! São classes derivadas de nodo! Um PATRÃO é uma PESSOA. Um EMPREGADO é uma PESSOA. Em qualquer caso, é possível encarar um “Patrão” ou um “Empregado” como sendo pessoas! Têm é “mais funcionalidade”. São classes derivadas! 20 Classes Patrão e Empregado Patrao.h class Patrao : public Pessoa { private: string _codigoEmpresa; public: Patrao(string nome, int idade, string codigo); void abreCofre(); }; Empregado.h class Empregado : public Pessoa { public: Empregado(string nome, int idade); void abreBalcao(); }; Pessoa -_nome -_idade +Pessoa(in nome, in idade) +imprime() Patrao Empregado -_codigoEmpresa +Patrao(in nome, in idade, in codigo) +abreCofre() +imprime() +Empregado(in nome, in idade) +abreBalcao() +imprime() 21 Classes Patrão e Empregado (2) Patrao.cpp Patrao::Patrao(string nome, int idade, string codigo) : Pessoa(nome, idade), _codigoEmpresa(codigo) { } void Patrao::abreCofre() { cout << "Eheh, a abrir o cofre. Codigo: " << _codigoEmpresa << endl; } Empregado.cpp Empregado::Empregado(string nome, int idade) : Pessoa(nome, idade) { } void Empregado::abreBalcao() { cout << "Puff.. a abrir o balcao." << endl; } 22 E agora, a execução... Patrao patrao("Carlos Manuel", 30, "1234F"); Empregado empregado("Guilherme To", 23); patrao.imprime(); patrao.abreCofre(); empregado.imprime(); empregado.abreBalcao(); 23 Interlúdio... Antes de podermos examinar o que é polimorfismo, temos de lidar com alguns detalhes... 24 Passagem por Valor, Referência e por Ponteiro Em C++, os parâmetros das funções podem ser passados por valor, por referência ou por ponteiro. Na passagem por valor, apenas uma cópia do valor é passado. Dentro da rotina, alterações na variável não afectam a variável original. Na passagem por referência, a variável que se encontra no parâmetro representa a variável original. Alterações na variável são reflectidas na variável original. Na passagem por ponteiro, é passado o endereço de memória onde se encontra o dado original. É possível guardar informação sobre onde se encontram esse dado. 25 Passagem por Valor (C++) #include <iostream> using namespace std; void leNome(string nome) { nome = “Critical”; cout << nome << endl; } void main() { string nome = “XSoft”; Ao chamar-se leNome(), o valor de “nome” é copiado para dentro da função. Não existe nenhuma relação entre a variável “nome” de leNome() e a variável “nome” do programa principal, excepto o seu valor inicial. Imprime… Critical XSoft leNome(nome); cout << nome << endl; } 26 Passagem por Referência (C++) #include <iostream> using namespace std; void leNome(string& nome) { nome = “Critical”; cout << nome << endl; } void main() { string nome = “XSoft”; Ao chamar-se leNome(), “nome” em leNome() representa a variável original com a qual se chama o programa (note-se que não têm de ter o mesmo nome). Alterações feitas sobre variável na função reflectem-se na variável original! Imprime… Critical Critical leNome(nome); cout << nome << endl; } 27 Ponteiros (C++) Em C++ moderno, a principal utilidade do uso de ponteiros é permitir guardar uma referência para um certo objecto. Notação... // Uma string string nome = “Critical”; // Um ponteiro para uma string... string* ptrNome = 0; // Uma atribuição... ptrNome = &nome; nome (0x5490) “Critical” ptrNome (0x6000) 0 ptrNome (0x6000) 0x5490 // Acesso à string original... cout << *ptrNome << endl; 28 Passagem de Ponteiros (C++) string* ptrNome = 0; void guardaNome(string* nome) { ptrNome = nome; } void imprimeNome() { cout << *ptrNome << endl; } Em C++ moderno, a principal utilidade do uso de ponteiros é permitir guardar uma referência para um certo objecto, para ser utilizado mais tarde. Imprime… XSoft Critical void main() { string nome = “XSoft”; guardaNome(&nome); imprimeNome(); nome = “Critical”; imprimeNome(); } 29 Polimorfismo Capacidade de objectos diferentes se comportarem de forma diferente quando recebem a mesma mensagem. Permite tratar da mesma forma todos os objectos. (uma mensagem é uma invocação de um método...) Um PATRÃO quando “calcula o ordenado”, calcula-o como sendo uma percentagem dos lucros da empresa. Um EMPREGADO, calcula-o como sendo um valor constante. Quando se chama imprime() em PATRÃO, deve dizer que “é um patrão” antes de dar os dados. Em empregado, deverá dizer que é um empregado. Patrao patrao(“Sofia", 30, "1234F"); Empregado empregado(“Carlos", 23); Pessoa* umaPessoa; Imprime… PATRAO[Sofia / 30] EMPREGADO[Carlos / 23] umaPessoa = &patrao; umaPessoa->imprime(); umaPessoa = &empregado; umaPessoa->imprime(); Comportamento diferente apesar de ser o mesmo código! 30 A classe Pessoa tem de ser modificada... Pessoa.h class Pessoa { private: string _nome; int _idade; public: Pessoa(string nome, int idade); virtual void imprime(); }; A palavra chave “virtual” indica que o método foi pensado para ser modificado numa classe derivada. Quando se usa um ponteiro do tipo Pessoa*, o ambiente de execução tem de procurar saber qual é a verdadeira classe do objecto e chamar o método correcto Sempre que se declara um método virtual, deve criar-se um “destrutor” virtual. Nós não iremos abordar esses tópicos neste crash course. Mas, no mundo real, se isto não for feito por levar a graves memory leaks! 31 Redefinição dos métodos imprime() Patrao.h Patrao.cpp class Patrao : public Pessoa { private: string _codigoEmpresa; void Patrao::imprime() { cout << “PATRAO”; Pessoa::Imprime(); } public: Patrao(string nome, int idade, string codigo); void abreCofre(); virtual void imprime(); }; Empregado.h Empregado.cpp class Empregado : public Pessoa { public: Empregado(string nome, int idade); void Empregado::imprime() { cout << “EMPREGADO”; Pessoa::Imprime(); } void abreBalcao(); virtual void imprime(); }; 32 A execução... 33 Herança, Polimorfismo e Redefinição Um aspecto muito importante do uso da herança é que todas as partes comuns, mesmo em termos de algoritmos, devem ser agrupadas na classe base. As classes abaixo são especializações Quando se redefine um método, pode sempre invocar-se a funcionalidade que já se encontrava disponível. E.g. Pessoa::imprime() Pessoa -_nome -_idade +Pessoa(in nome, in idade) +imprime() Patrao Empregado -_codigoEmpresa +Patrao(in nome, in idade, in codigo) +abreCofre() +imprime() +Empregado(in nome, in idade) +abreBalcao() +imprime() 34 » Now this is not the end. It is not even the beginning of the end. But it is, perhaps, the end of the beginning « Winston Churchill Questions? 35 Moth found trapped between points at Relay # 70, Panel F, of the Mark II Aiken Relay Calculator while it was being tested at Harvard University, 9 September 1945. The operators affixed the moth to the computer log, with the entry: “First actual case of bug being found”. They put out the word that they had “debugged” the machine, thus introducing the term “debugging a computer program”. In 1988, the log, with the moth still taped by the entry, was in the Naval Surface Warfare Center Computer Museum at Dahlgren, Virginia. U.S. Naval Historical Center Photograph. http://www.history.navy.mil/index.html 36 Programação Orientada aos Objectos Set/2005 “Network Simulator” Um pequeno caso de estudo Paulo Marques Departamento de Eng. Informática Universidade de Coimbra [email protected] Aviso! O exemplo que se segue é limitado e serve unicamente para ilustrar os pontos anteriormente explorados Em C++ real, existem algumas coisas que são feitas neste código que não devem ser feitas Eventuais problemas com memory leaks Performance... péssima Porquê é que o fazemos? Infelizmente, nesta “short session”, não é possível estudar a linguagem a fundo... » It has been discovered that C++ provides a remarkable facility for concealing the trival details of a program – such as where its bugs are « David Keppel 38 Simple Network B E A C D F 39 Simple Network (2) Temos NODOS Cada NODO pode ser um COMPUTADOR ou um REPETIDOR Os nodos enviam() e recebem() PACTOREs Os NODOS pertencem a uma SUBREDE Uma SUBREDE pode ter vários NODOS Um COMPUTADOR apenas pertence a uma SUBREDE Um REPETIDOR pode pertencer a várias SUBREDEs 40 Diagrama UML Subnet * 1 +addNode() +broadcast() +getName() +sendPacket() +receivePacket() +addToSubnet() 1 1 Computer Packet -_from -_to -_msg +getOrigin() +getDestination() +getMessage() +print() -_name -_nodes * «uses» Node Bridge -subnet -subnets +sendPacket() +receivePacket() +addToSubnet() +sendPacket() +receivePacket() +addToSubnet() 1 41 Comportamento do Sistema COMPUTADOR: REPETIDOR: Se eu quero enviar um pacote faço broadcast para a minha sub-rede Se eu recebo um pacote caso seja para mim, imprimo-o caso contrário, descarto-o Se eu recebo um pacote caso seja para mim, imprimo-o caso contrário, envio-o para todas as redes a que estou ligado SUB-REDE Caso me peçam para fazer um broadcast de um pacote envio para todos os nodos excepto o de origem 42 Implementação – Programa Principal (Network.cpp) int main() Computer Computer Computer { computerA("Computer_A"); computerB("Computer_B"); computerC("Computer_C"); Computer computerD("Computer_D"); Computer computerE("Computer_E"); Computer computerF("Computer_F"); B A E C D F Bridge bridge("MyBridge"); Subnet subnet1; subnet1.addNode(computerA); subnet1.addNode(computerB); subnet1.addNode(computerC); subnet1.addNode(bridge); Subnet subnet2; subnet2.addNode(computerD); subnet2.addNode(computerE); subnet2.addNode(computerF); subnet2.addNode(bridge); Packet p1("Computer_A", "Computer_F", "Hello F!"); computerA.sendPacket(p1); } 43 O Resultado da Execução... 44 Como implementar uma relação 1 para 1? Computer Subnet -subnet +sendPacket() +receivePacket() +addToSubnet() 1 1 -_nodes +addNode() +broadcast() class Computer : public Node { private: Subnet* _subnet; public: void addToSubnet(Subnet& s); }; void Computer::addToSubnet(Subnet& net) { _subnet = &net; } 45 Como implementar uma relação 1 para N? Bridge Subnet -subnets +sendPacket() +receivePacket() +addToSubnet() 1 * -_nodes +addNode() +broadcast() class Bridge : public Node { private: vector<Subnet*> _subnets; public: void addToSubnet(Subnet& s); }; void Bridge::addToSubnet(Subnet& net) { _subnets.push_back(&net); } 46 Interface – Node.h class Node { private: string _name; public: Node(string name); string getName(); virtual void sendPacket(Packet p) = 0; virtual void receivePacket(Packet p) = 0; virtual void addToSubnet(Subnet& net) = 0; }; 47 Interface – Computer.h e Bridge.h class Computer : public Node { private: Subnet* _subnet; public: Computer(string name); virtual void addToSubnet(Subnet& net); virtual void sendPacket(Packet p); virtual void receivePacket(Packet p); }; class Bridge : public Node { private: vector<Subnet*> _subnets; public: Bridge(string name); virtual void addToSubnet(Subnet& net); virtual void sendPacket(Packet p); virtual void receivePacket(Packet p); }; 48 Interface – Subnet.h e Packet.h class Subnet { private: vector<Node*> _nodes; public: Subnet(); void addNode(Node& node); void broadcast(Node& sender, Packet p); }; class Packet { private: string _from; string _to; string _msg; public: Packet(string from, string to, string msg); string getOrigin(); string getDestination(); string getMessage(); void print(); }; 49 Implementação – Packet.cpp Packet::Packet(string from, string to, string msg) : _from(from), _to(to), _msg(msg) { } string Packet::getOrigin() { return _from; } string Packet::getDestination() { return _to; } string Packet::getMessage() { return _msg; } void Packet::print() { cout << "[FROM: " << _from << ", TO: " << _to << ", MSG: \"" << _msg << "\"]" << endl; } 50 Implementação – Node.cpp Node::Node(string name) : _name(name) { } string Node::getName() { return _name; } 51 Implementação – Computer.cpp Computer::Computer(string name) : Node(name), _subnet(0) { } void Computer::addToSubnet(Subnet& net) { _subnet = &net; } void Computer::sendPacket(Packet p) { _subnet->broadcast(*this, p); } void Computer::receivePacket(Packet p) { if (p.getDestination() == getName()) { cout << "[NODE " << getName() << "] RECEIVED MESSAGE: "; p.print(); } else { cout << "{Node " << getName() << "} Discarting packet: "; p.print(); } } 52 Implementação – Bridge.cpp Bridge::Bridge(string name) : Node(name) { } void Bridge::addToSubnet(Subnet& net) { _subnets.push_back(&net); } void Bridge::sendPacket(Packet p) { for (unsigned i=0; i<_subnets.size(); i++) _subnets[i]->broadcast(*this, p); } void Bridge::receivePacket(Packet p) { if (p.getDestination() == getName()) { cout << "[BRIDGE " << getName() << "] RECEIVED MESSAGE: "; p.print(); } else { cout << "*Bridge " << getName() << "* Echoing packet: "; p.print(); sendPacket(p); } } 53 Implementação – Subnet.cpp Subnet::Subnet() { } void Subnet::addNode(Node& node) { node.addToSubnet(*this); _nodes.push_back(&node); } void Subnet::broadcast(Node& sender, Packet p) { for (unsigned i=0; i<_nodes.size(); i++) if (&sender != _nodes[i]) _nodes[i]->receivePacket(p); } 54 That’s It! B E A C D F 55 Alguns exercícios... O que é que acontece se forem colocadas dois repetidores na rede? Como é que resolve o problema? Considere uma rede com fios em que existem encaminhadores (routers). Modele e implemente um sistema que permita enviar pacotes de qualquer endereço para qualquer endereço. Considere um jogo de cartas (Poker, Sueca, ...). Modele o sistema e simule o decorrer de um jogo: Pessoa contra Pessoa Pessoa contra Computador Computador contra Computador 56 Para saber mais... C++ How to Program, 4th Edition by Harvey M. Deitel, Paul J. Deitel Prentice Hall, Aug. 2002 Uma introdução “leve” ao C++ C++ Primer, 4th Edition by Stanley B. Lippman et. al. Addison-Wesley Professional, Feb. 2005 Para aprender “ao pormenor” tudo o que há a saber sobre C++ 57 Para saber mais... Object-Oriented Analysis and Design with Applications, 2nd Edition by Grady Booch Addison-Wesley Professional, Sep. 1993 Como fazer design orientado aos objectos! Notação desactualizada, mas uma referência; Muito educativo! The Unified Modeling Language User Guide, 2nd Ed. by Grady Booch, James Rumbaugh, Ivar Jacobson Addison-Wesley Professional, May 2005 Um “tutorial” extremamente claro sobre como fazer modelação usando a linguagem UML. 58 YOU ARE FREE TO USE THIS MATERIAL FOR YOUR PERSONAL LERNING OR REFERENCE, DISTRIBUTE IT AMONG COLLEGUES OR EVEN USE IT FOR TEACHING CLASSES. YOU MAY EVEN MODIFY IT, INCLUDING MORE INFORMATION OR CORRECTING STANDING ERRORS. THIS RIGHT IS GIVEN TO YOU AS LONG AS YOU KEEP THIS NOTICE AND GIVE PROPER CREDIT TO THE AUTHOR. YOU CANNOT REMOVE THE REFERENCES TO THE AUTHOR OR TO THE INFORMATICS ENGINEERING DEPARTMENT OF THE UNIVERSITY OF COIMBRA. (c) 2005 – Paulo Marques, [email protected] 59