Tópicos Adicionais em Projeto de
Sistemas
Projeto de Sistemas
Herança versus Composição
• Composição e herança são dois mecanismos para reutilizar funcionalidade
• Alguns anos atrás (e na cabeça de alguns programadores ainda!), a herança era considerada a ferramenta básica de extensão e
reutilização de funcionalidade
• A composição estende uma classe pela delegação de trabalho para outro objeto
• A herança estende atributos e métodos de uma classe
• Hoje, considera-se que a composição é muito superior à herança na maioria dos casos
Vantagens e Desvantagens
• As grandes vantagens da composição são:
– A herança gera um acoplamento muito forte. A
composição pode reduzir acoplamento, principalmente se interfaces forem usadas
– A composição pode ser mudada em tempo de execução
• A grande vantagem da herança é que ela gera código
mais simples de ler e entender
– A composição usa muitos objetos pequenos e só sabemos quem vai ser composto com quem em tempo de execução – Isso é mais difícil de entender
Quando usar composição
• Use composição para estender as responsabilidades pela
delegação de trabalho a outros objetos
• Um exemplo no domínio de endereços
– Uma empresa tem um endereço (digamos só um) – Uma empresa "tem" um endereço
– Podemos deixar o objeto empresa responsável pelo objeto endereço e temos agregação composta (composição)
Quando usar herança
• Atributos, conexões a objetos e métodos
comuns vão na superclasse (classe de
generalização)
• Adicionamos mais dessas coisas nas subclasses
(classes de especialização)
Um exemplo de herança
• Exemplo no domínio de
reserva e compra de
passagens de avião
• Uma transação é um
momento notável ou
intervalo de tempo
Benefícios da herança
• Captura o que é comum e o isola daquilo que
é diferente
• A herança é vista diretamente no código
• Código mais fácil de entender
Problemas da herança
• O encapsulamento entre classes e subclasses é fraco (o
acoplamento é forte)
– Mudar uma superclasse pode afetar todas as subclasses
• The weak base-class problem
– Isso viola um dos princípios básicos de projeto O-O (manter fraco acoplamento)
• Às vezes um objeto precisa ser de uma classe diferente em
momentos diferentes
– Com herança, a estrutura está parafusada no código e não pode sofrer alterações facilmente em tempo de execução
Um exemplo de problema da herança
• Problema: uma pessoa
pode mudar de papel a
assumir combinações de
papeis
• Fazer papeis múltiplos
requer 7 combinações
(subclasses)
Solucionando o problema com
composição
Solucionando o problema com
composição
• Estamos estendendo a funcionalidade de Pessoa de várias formas, mas sem usar herança.
• Observe que também podemos inverter a composição (uma pessoa tem um ou mais papeis). Pense na implicação para a interface de “pessoa”.
• Aqui, estamos usando delegação: dois objetos estão envolvidos em atender um pedido (setNome). O objeto tripulação delega
setNome para o objeto pessoa que ele tem por composição.
• Técnica também chamada de forwarding.
• É semelhante a uma subclasse delegar uma operação para a superclasse (herdando a operação).
Delegação sempre pode ser usada para
substituir a herança
• Se usássemos herança, o objeto tripulação poderia referenciar a pessoa com this
• Com o uso de delegação, tripulação pode passar this para Pessoa e o objeto Pessoa pode referenciar o objeto original se quiser
• Em vez de tripulação ser uma pessoa, ele tem uma pessoa
• A grande vantagem da delegação é que o comportamento pode ser escolhido em tempo de execução em vez de estar amarrado em tempo de compilação
• A grande desvantagem é que um software muito dinâmico e parametrizado é mais difícil de entender do que software mais estático
O resultado de usar composição
• Em vez de codificar um comportamento estaticamente,
definimos pequenos comportamentos padrão e usamos
composição para definir comportamentos mais complexos
• De forma geral, a composição é melhor do que herança
normalmente, pois:
– Permite mudar a associação entre classes em tempo de execução;
– Permite que um objeto assuma mais de um comportamento (ex. papel);
Cinco regras para o uso de herança
(Regras de Coad)
1. O objeto "é um tipo especial de" e não "um papel assumido por"
2. O objeto nunca tem que mudar para outra classe
3. A subclasse estende a superclasse mas não faz override ou anulação de variáveis e/ou métodos*
4. Não é uma subclasse de uma classe “utilitária”**
5. Para classes do domínio do problema, a subclasse expressa tipos especiais de papeis, transações ou dispositivos
** Não é uma subclasse de uma classe
"utilitária"
• Não é uma boa idéia fazer isso porque herdar de, digamos,
HashMap deixa a classe vulnerável a mudanças futuras à classe HashMap
• O objeto original não "é" um HashMap (mas pode usá-lo) • Não é uma boa idéia porque enfraquece o encapsulamento
– Clientes poderão supor que a classe é uma subclasse da classe utilitária e não funcionarão se a classe eventualmente mudar sua superclasse – Exemplo: x usa y que é subclasse de Vector
• x usa y sabendo que é um Vector
• Amanhã, y acaba sendo mudada para ser subclasse de HashMap • x se lasca!
Exemplo de aplicação das regras
• Considere Agente, Tripulação e Passageiro como
subclasses de Pessoa
– Regra 1 (tipo especial): não passa. Um Passageiro não é um tipo especial de Pessoa: é um papel assumido por uma Pessoa – Regra 2 (mutação): não passa. Um Agente pode se
transformar em Passageiro com o tempo – Regra 3 (só estende): ok.
– Regra 4: ok.
– Regra 5: não passa. Passageiro está sendo modelado como tipo especial de Pessoa e não como tipo especial de papel
Outro exemplo: transações
• Reserva e Compra podem herdar de Transação?
– Regra 1 (tipo especial): ok. Uma Reserva é um tipo especial de Transação e não um papel assumido por uma Transação
– Regra 2 (mutação): ok. Uma reserva sempre será uma Reserva, e nunca se transforma em Compra (se houver uma compra da
passagem, será outra transação). Idem para Compra: sempre será uma Compra
– Regra 3 (só estende): ok. Ambas as subclasses estendem Transação com novas variáveis e métodos e não fazem override ou anulam coisas de Transação
– Regra 4 (não estende classe utilitária): ok
– Regra 5 (tipo especial de papel/transação/dispositivo): ok. São tipos especiais de Transação
IMPLEMENTAÇÃO DE ASSOCIAÇÕES
(VISIBILIDADE DE OBJETOS)
Implementação de associações: exemplo
• De onde vem o cliente do pedido? Como
pedido ganha uma referência para um cliente?
• Pelo menos seis diferentes maneiras de ter
acesso a uma referência são possíveis
(Arthur Riel, Object-Oriented Design Heuristics)
Implementação de associações
• Atributos referenciais
– Passados como argumentos de construtores
– Construídos dentro da classe
– Obtidos de terceiros
– Combinando construtores e mutators
• Objetos temporários
– Passados como argumentos de métodos
– Criados on-the-fly dentro de um método
Atributos referenciais
• Atributo referencial: atributo de um objeto
que se refere a outro objeto, ainda que este
não esteja logicamente contido na classe que
mantém o atributo
Atributos referenciais: passados como
argumentos de construtores
• Ao construir um objeto, passe a referência já
existente do outro objeto como parâmetro do
construtor
• Depois, torne o parâmetro um atributo
referencial
• Esta situação assume que o outro objeto já
existe inicializado
Atributos referenciais: passados como
argumentos de construtores
public class SalesInvoice {
// --Constructors---public SalesInvoice( Customer soldTo )
{
this.soldTo = soldTo;
// Other initializations }
// Details omitted
// --Class and Object Attributes---private Customer soldTo;
Atributos referenciais: construídos dentro
da classe
• Ao comprar algo, você tem duas opções:
– Preenche uma solicitação de crédito e depois
preenche o pedido de compra;
– Preenche o pedido de compra e depois faz uma
verificação do crédito do cliente.
• No último caso, pode-se criar uma referência
ao cliente durante ou após a construção do
pedido.
Atributos referenciais: construídos dentro
da classe
public class SalesInvoice {
// --Constructors---public SalesInvoice( ... )
{
this.soldTo = new Customer(); // Other initializations
}
// Details omitted
// --Class and Object Attributes---private Customer soldTo;
Atributos referenciais: obtidos de
terceiros
• Há um repositório de objetos disponível
• Exemplo: localize um cliente pelo número do
cartão fidelidade (parâmetro)
• O parâmetro é usado para construir o objeto
pedido e dentro dele o objeto cliente
Atributos referenciais: obtidos de
terceiros
public class SalesInvoice {
// --Constructors---public SalesInvoice( int custNo, ... )
{
this.soldTo = CustomerDB.getCustomer(custNo); // Other initializations
}
// Details omitted
// --Class and Object Attributes---private Customer soldTo;
Atributos referenciais: combinando
construtores e mutators
• A solução anterior padece do problema de ainda ter
de fornecer um parâmetro (mesmo que seja só
numérico). O cliente tem de ser conhecido de
qualquer jeito.
• Solução:
– dê dois construtores a pedido, sendo que o construtor default pega um cliente default (cliente cash)
– crie um método mutator em Pedido que altera o cliente
Atributos referenciais: combinando
construtores e mutators
public class SalesInvoice { // =============================== // CONSTRUCTORS // =============================== public SalesInvoice( Customer
soldTo, Customer shipTo, SalesPerson soldBy, LineItems items ) { this.soldTo = soldTo; this.shipTo = shipTo; this.soldBy = soldBy; this.items = items; } public SalesInvoice( ) { this( Customer.getCustomer(0), null, new SalesPerson(), null);
this.items = new LineItems(5, this ); }
// Details omitted //
=================================== // CLASS AND OBJECT ATTRIBUTES
//
=================================== private Customer soldTo;
private Customer shipTo; private SalesPerson soldBy; private LineItems items; }
Objetos temporários
• Em algumas situações, você não quer que a
associação seja permanente
• Às vezes, um objeto pode ter um relacionamento
muito curto e transitório com outro objeto
• Pode ser necessário usar um objeto somente em um
só método, sem precisar guardar seu estado
• A solução é criar um objeto temporário (variável
local) para uso pela duração de um método
Objetos temporários: passados como
argumentos de métodos
• Uma primeira possibilidade seria passar o objeto
como argumento de um método. Isto é apropriado
quando:
– O estado do objeto não precisa ser retido entre duas chamadas do método
– O objeto carrega com ele informações de estado que têm de ser atualizadas cada vez que o método é chamado (por exemplo: public static void paint
(Graphics g))
– O objeto é construído facilmente fora de seu ambiente de classe)
Objetos temporários: criados on-the-fly
dentro de um método
• Vantagens em relação a passar como parâmetro:
– torna a chamada do método mais simples
SomeObject obj = new SomeObject(); obj.doSomeGraphicsThing();
em vez de:
SomeObject obj = new SomeObject(); Graphics g = obj.getGraphics(); obj.doSomeGraphicsThing( g );
– Torna o código mais fácil de manter
• Usado quando o objeto usuário tem conhecimento
especial que é requerido para criar o objeto que será
usado
Objetos temporários: criados on-the-fly
dentro de um método
public void doSomeGraphicsThing()
{
Graphics g = getGraphics();
g.setFont(...);
g.dispose();
}
Regras para o uso de objetos
• Use um atributo referencial quando:
– O objeto é usado em vários métodos diferentes ou o objeto armazena informação de estado persistente entre chamadas de métodos
– O objeto é usado repetidamente
– É muito custoso construir o objeto e você o utiliza mais de uma vez
• Passe um objeto como argumento de um método quando:
– O objeto será usado em um só método
– É fácil construir o objeto fora de sua classe (o objeto a ser usado traz informação suprida por quem chamou o método)
• Construa o objeto on-the-fly quando:
– o objeto será usado só naquele método
DETALHES ADICIONAIS SOBRE
Multiplicidade de atributos referenciais
[1..*]
Visibilidade de atributos e métodos
Person + name : String # address : Address # birthdate : Date ~ phone : String / age : Date - ssn : IdAtributos e métodos podem ser: + public
# protected - private
~ default (package, friendly) / derived
Classe de Associação
• São classes produzidas quando da ocorrência
de associações que possuem multiplicidade
muitos (*) em ambas as extremidades.
• São necessárias nesses casos porque não
existe um repositório que possa armazenar as
informações produzidas pelas associações
Classe de Associação
Tournament Player * * Statistics + getAverageStat(name) + getTotalStat(name) + updateStats(match) Registration modelNumber serialNumber warrentyCodeTransformação de uma classe de
associação
Tournament Player
* *
Modelo de design antes da transformação
Modelo de design depois da transformação: 1 classe e 2 associações binárias Statistics + getAverageStat(name) + getTotalStat(name) + updateStats(match) Statistics + getAverageStat(name) + getTotalStat(name)
Associação qualificada
Modelo de design depois da transformação
Player nickName * 0..1 League Player * *
Modelo de design antes da transformação
League
nickName
Associação qualificada unidirecional
public class League {private Map players;
public void addPlayer
(String nickName, Player p) {
if (! players.containsKey(nickName)) { players.put(nickName, p); p.addLeague(nickName, this); } } }
public class Player {
} Código-fonte depois da transformação
Associação qualificada bidirecional
public class League {private Map players;
public void addPlayer
(String nickName, Player p) {
if (! players.containsKey(nickName)) { players.put(nickName, p); p.addLeague(nickName, this); } } }
public class Player { private Map leagues; public void addLeague
(String nickName, League l) {
if (!leagues.containsKey(l)) { leagues.put(l, nickName); l.addPlayer(nickName, this); } } } Código-fonte depois da transformação
Classe parametrizada (template)
LinkedList T add(T) remove(T) T 1 .. *Uma classe parametrizada ou template define uma família de elementos
potenciais de outra classe.
Para usá-la, o parâmetro deve ser ligado (bound).
Um template é representado por um
pequeno retângulo tracejado superposto no canto superior direito do retângulo da classe. O retângulo tracejado contém uma lista dos parâmetros formais para a