Padrões Para Atribuir Responsabilidades

Introdução
- Um sistema OO é composto de objetos que enviam mensagens uns para os outros
- Uma mensagem é um método executado no contexto de um objeto
- Escolher como distribuir as responsabilidades entre objetos (ou classes) é crucial para
um bom projeto
- Uma má distribuição leva a sistemas e componentes frágeis e difíceis de entender,
manter, reusar e estender
- Mostraremos alguns padrões de distribuição de
responsabilidades
- Padrões GRASP (General Responsibility Assignment Software Patterns)
- São padrões de Larman
- Ao mostrar padrões, apresentaremos princípios de um bom projeto
OO
- Veremos mais padrões de projeto em outros capítulos
- Os padrões são utilizados enquanto se cria diagramas de interação
Responsabilidades
- Responsabilidades são obrigações de um tipo ou de uma classe
- Obrigações de fazer algo
- Fazer algo a si mesmo
- Iniciar ações em outros objetos
- Controlar ou coordenar atividades em outros objetos
- Obrigações de conhecer algo
- Conhecer dados encapsulados
- Conhecer objetos relacionados
- Conhecer coisas que ele pode calcular
- Exemplos
- Uma Venda tem a responsabilidade de criar linha de detalhe (fazer algo)
- Uma Venda tem a responsabilidade de saber sua data (conhecer algo)
- Granularidade
- Uma responsabilidade pode envolver um único método (ou poucos)
- Exemplo: Criar uma linha de detalhe
- Uma reponsabilidade pode envolver dezenas de classes e métodos
- Exemplo: Responsabilidade de fornecer acesso a um BD
- Uma responsabilidade não é igual a um método
- Mas métodos são usados para implementar responsabilidades
Diagramas de interação evidenciam responsabilidades

- O diagrama indica que objetos da classe Venda têm responsabilidade de criar linhas de
detalhe de venda
- Eles devem colaborar com objetos da classe LinhaDetalheVenda
Padrões
- Uma definição informal:
- "Cada padrão descreve um problema que ocorre frequentemente e então descreve o
cerne da solução ao problema de forma a poder reusar a solução milhões de vezes em
situações diferentes"
- Observe que o que é reutilizado são as classes e suas colaborações
- Reuso de idéias, não código
- Consistem de micro-arquiteturas de classes, objetos, suas responsabilidades e suas
colaborações
- Contêm o somatório da experiência dos melhores projetistas O-O!
- Estão revolucionando o projeto de software desde 1995 quando o famoso livro da
"Gang of Four" (GoF) apareceu com o primeiro catálogo de 23 padrões
- Ver bibliografia
- Tem muito mais padrões aparecendo sempre
- OOPSLA é uma grande fonte de padrões
- Ficará mais claro com alguns exemplos
- Design Patterns iniciaram a febre de padrões
- Analysis Patterns
- Testing Patterns
- Business Patterns
- Pedagogical Patterns
- e mais ...
- Os padrões GRASP são de Larman e são do tipo Design Patterns
- Veremos padrões GRASP e GoF (em outro capítulo)
Elementos essenciais de um Design Pattern
Um Nome
- Descreve o problema de projeto, suas soluções e consequências em poucas palavras
- Permite projetar num nível mais alto de abstração
- Permite falar com outros sobre soluções e documentar código, já que os nomes de
padrões estão ficando padronizados
- "Todo mundo" conhece os 23 padrões da GoF
- É equivalente a padronizar "lista encadeada", "pilha", etc. no
mundo das estruturas de dados
- Cuidado! Nem todo mundo conhece os padrões de Larman
O Problema
- Descreve quando aplicar o padrão
- Descreve o problema e o contexto
- Pode descrever problemas específicos de projeto
- Exemplo: como representar algoritmos como objetos?
- Pode descrever estruturas de objetos ou de classes que são sintomas de um projeto
inflexível
- Às vezes, o padrão lista condições que devem se aplicar para usar o padrão
A Solução
- Descreve os elementos constituintes do projeto, seus relacionamentos, responsabilidades
e colaborações
- A solução não descreve um projeto ou implementação concretos porque um padrão é
um gabarito de solução para várias situações
As Consequências
- Os resultados e trade-offs da aplicação do padrão
- Diz respeito a trade-offs de espaço, tempo, flexibilidade, extensibilidade,
portabilidade
Os padrões GRASP deste capítulo
Problema
- Qual é o princípio mais fundamental para atribuir responsabilidades?
Solução
- Atribuir uma responsabilidade ao expert de informação - a classe
que possui a informação necessária para preencher a responsabilidade
Exemplo
- No estudo de caso, alguma classe precisa saber o total de uma venda
- Podemos dizer isso sobre a forma de uma responsabilidade:
- Quem deveria ser reponsável pelo conhecimento do total de uma
venda?
- Pelo padrão Expert, escolhemos a classe que possui a informação necessária para
determinar o total
- Considere uma parte do modelo conceitual:

- Qual é a informação necessária?
- Precisamos conhecer (ter acesso a) todos os LinhaDetalheVenda
- Qual é information expert?
- É a classe Venda
- Podemos agora fazer parte do diagrama de colaboração e do diagrama
de classes

- Ainda não terminamos. Qual informação é necessária para determinar o subtotal para
um item (uma linha de detalhe)?
- Precisamos de LinhaDeVenda.quantidade e de EspecificaçãoDeProduto.preço
- Quem é o information expert?
- É a classe LinhaDeVenda
- Chegamos aos seguintes diagramas:

- Qual é o information expert para saber o preço de um produto?
- Chegamos aos seguintes diagramas:

Discussão
- É o padrão mais usado de todos para atribuir responsabilidades
- A informação necessária frequentemente está espalhada em vários objetos
- Portanto, tem muitos experts parciais
- Exemplo: determinar o total de uma venda requer a colaboração de 3 objetos, em 3
classes diferentes
- Mensagens são usadas para estabelecer as colaborações
- O resultado final é diferente do mundo real
- No mundo real, uma venda não calcula seu próprio total
- Isso seria feito por uma pessoa (se não houvesse software)
- Mas no mundo de software, não queremos atribuir essa responsabilidade ao Caixa ou ao
TPDV!
- No mundo de software, coisas inertas (ou até conceitos como uma venda) podem ter
responsabilidades: Tudo está vivo!
Consequências
- A encapsulação é mantida, já que objetos usam sua
própria informação para cumprir suas responsabilidades
- Leva a fraco acoplamento entre objetos e sistemas mais
robustos e fáceis de manter
- Leva a alta coesão, já que os objetos fazem tudo que é
relacionado à sua própria informação
Também conhecido como
- "Colocar as responsabilidades com os dados"
- "Quem sabe, faz"
- "Animação"
- "Eu mesmo faço"
- "Colocar os serviços junto aos atributos que eles manipulam"
Problema
- Quem deve criar novas instâncias de uma classe?
Solução
- Atribuir à classe B a responsabilidade de criar instância da
classe A se uma das seguintes condições se aplicar:
- B agrega objetos da classe A
- B contém objetos da classe A
- B registra instâncias da classe A
- B usa muito objetos da classe A
- B possui os dados usados para inicializar A
- B é um criador (creator) de objetos da classe A
- Se mais de uma opção se aplica, escolha o B que agregue ou contenha objetos da classe
A
Exemplo
- No estudo de caso, quem deveria criar uma instância de LinhaDetalheVenda?
- Pelo padrão Creator, precisamos achar alguém que agrega, contém, ... instâncias de
LinhaDetalheVenda
- Considere o modelo conceitual parcial abaixo:

- Venda agrega instâncias de LinhaDetalheVenda e é portanto um bom candidato para criar
as instâncias
- Chegamos aos seguintes diagramas:

Discussão
- Escolhemos um criador que deve estar conectado ao objeto criado, de qualquer forma,
depois da criação
- Isso leva a fraco acoplamento
- Exemplo de criador que possui os valores de inicialização
- Uma instância de Pagamento deve ser criada
- A instância deve receber o total da venda
- Quem tem essa informação? Venda
- Venda é um bom candidato para criar objetos da classe Pagamento
Consequências
- Fraco acoplamento, já que o objeto criado deve normalmente ser visível ao criador,
depois da criação
Problema
- Como minimizar dependências e maximizar o reuso?
- O acoplamento é uma medida de quão fortemente uma classe
está conectada, possui conhecimento ou depende de outra classe
- Com fraco acoplamento, uma classe não é dependente de muitas outras classes
- Com uma classe possuindo forte acoplamento, temos os seguintes problemas:
- Mudanças a uma classe relacionada força mudanças locais à classe
- A classe é mais difícil de entender isoladamente
- A classe é mais difícil de ser reusada, já que depende da presença de outras classes
Solução
- Atribuir responsabilidades de forma a minimizar o acoplamento
Exemplo
- Considere o seguinte diagrama parcial de classes no estudo de caso

- Suponha que temos que criar um Pagamento e associá-lo a uma Venda
- Que classe deveria ter essa responsabilidade?
- Alternativa 1: No mundo real, um TPDV "registra" um pagamento e o padrão
Creator sugere que TPDV poderia criar Pagamento
- TPDV deve então passar o pagamento para a Venda
- Veja o resultado abaixo

- Alternativa 2: Criar o Pagamento com Venda e associá-lo à Venda

- Supondo que a Venda deva ter conhecimento do pagamento (depois da criação) de qualquer
jeito, a alternativa 2 tem menos acoplamento (TPDV não está acoplado a Pagamento)
- Dois padrões (Creator e Low Coupling) sugeriram diferentes soluções
- Minimizar acoplamento ganha
Discussão
- Minimizar acoplamento é um dos princípios de ouro do projeto OO
- Acoplamento de manifesta de várias formas:
- X tem um atributo que referencia uma instância de Y
- X tem um método que referencia uma instância de Y
- Pode ser parâmetro, variável local, objeto retornado pelo método
- X é uma subclasse direta ou indireta de Y
- X implementa a interface Y
- A herança é um tipo de acoplamento particularmente forte
- Uma seção futura esmiuça o assunto
- Não se deve minimizar acoplamento criando alguns poucos objetos monstruosos (God
classes)
- Exemplo: todo o comportamento numa classe e outras classes usadas como depósitos
passivos de informação
- Tipos de acoplamentos (do menos ruim até o pior)
- Acoplamento de dados
- Acoplamento de controle
- Acoplamento de dados globais
- Acoplamento de dados internos
- Acoplamento de dados
- Situações
- Saída de um objeto é entrada de outro
- Uso de parâmetros para passar itens entre métodos
- Ocorrência comum:
- Objeto a passa objeto x para objeto b
- Objeto x e b estão acoplados
- Uma mudança na interface de x pode implicar em mudanças a b
- Exemplo:
class Servidor {
public void mensagem(MeuTipo x) {
// código aqui
x.façaAlgo(Object dados); // dados e x estão acoplados
// (se interface de dados mudar x terá que mudar)
// mais código
}
}
- Exemplo pior:
- Objeto a passa objeto x para objeto b
- x é um objeto composto ou agregado (contém outro(s) objeto(s))
- Objeto b deve extrair objeto y de dentro de x
- Há acoplamento entre b, x, representação interna de x, y
- Exemplo: ordenação de registros de alunos por matrícula e nome
class Aluno {
String nome;
long matrícula;
public String getNome() { return nome; }
public long getMatrícula() { return matrícula; }
// etc.
}
ListaOrdenada listaDeAlunos = new ListaOrdenada();
Aluno novoAluno = new Aluno(...);
//etc.
listaDeAlunos.add(novoAluno);
- Agora, vamos ver os problemas
class ListaOrdenada {
Object[] elementosOrdenados = new Object[tamanhoAdequado];
public void add(Aluno x) {
// código não mostrado
long matrícula1 = x.getMatrícula();
long matrícula2 = elementosOrdenados[k].getMatrícula();
if(matrícula1 < matrícula2) {
// faça algo
} else {
// faça outra coisa
}
}
- O problema da solução anterior é que há forte acoplamento
- ListaOrdenada sabe muita coisa de Aluno
- O fato de que a comparação de alunos é feito com a matrícula
- O fato de que a matrícula é obtida com getMatrícula()
- O fato de que matrículas são long (representação de dados)
- Como comparar matrículas (com <)
- O que ocorre se mudarmos qualquer uma dessas coisas?
- Solução 2: mande uma mensagem para o próprio objeto se comparar com outro
class ListaOrdenada {
Object[] elementosOrdenados = new Object[tamanhoAdequado];
public void add(Aluno x) {
// código não mostrado
if(x.lessThan(elementosOrdenados[K])) {
// faça algo
} else {
// faça outra coisa
}
}
- Reduzimos o acoplamento escondendo informação atrás de um método
- Problema: ListaOrdenada só funciona com Aluno
- Solução 3: use interfaces para desacoplar mais ainda
Interface Comparable {
public boolean lessThan(Object outro);
public boolean greaterThan(Object outro);
public boolean equal(Object outro);
}
class Aluno implements Comparable {
public boolean lessThan(Object outro) {
// compare registro de aluno com outro
}
}
class ListaOrdenada {
Object[] elementosOrdenados = new Object[tamanhoAdequado];
public void add(Comparable x) {
// código não mostrado
if(x.lessThan(elementosOrdenados[K])) {
// faça algo
} else {
// faça outra coisa
}
}
- Em C++, teria outras soluções
- Apontador de função
- Apontador de função com tipos genéricos (templates)
- Acoplamento de controle
- Passar flags de controle entre objetos de forma que um objeto controle as etapas de
processamento de outro objeto
- Ocorrência comum:
- Objeto a manda uma mensagem para objeto b
- b usa um parâmetro da mensagem para decidir o que fazer
class Lampada {
public static final ON = 0;
public void setLampada(int valor) {
if(valor == ON) {
// liga lampada
} else if(valor == 1) {
// desliga lampada
} else if(valor == 2) {
// pisca
}
}
}
Lampada lampapa = new Lampada();
lampada.setLampada(Lampada.ON);
lampada.setLampada(2);
- Solução: decompor a operação em múltiplas operações primitivas
class Lampada {
public void on() { // liga lampada }
public void off() { // desliga lampada }
public void pisca() { // pisca }
}
Lampada lampada = new Lampada();
lampada.on();
lampada.pisca();
- Ocorrência comum:
- Objeto a manda mensagem para objeto b
- b retorna informação de controle para a
- Exemplo: retorno de código de erro
class Teste {
public int printFile(File toPrint) {
if(toPrint está corrompido ) {
return CORRUPTFLAG;
}
// etc. etc.
}
}
Teste umTeste = new Teste();
int resultado = umTese.printFile(miniTeste);
if(resultado == CORRUPTFLAG) {
// oh! oh!
} else if(resultado == -243) {
// etc. etc.
class Teste {
public int printFile(File toPrint) throws
PrintExeception {
if(toPrint está corrompido ) {
throw new PrintExeception();
}
// etc. etc.
}
}
try {
Teste umTeste = new Teste();
umTeste.printFile(miniTeste);
} catch(PrintException printError) {
// faça algo
}
- Acoplamento de dados globais
- Dois ou mais objetos compartilham estruturas de dados globais
- É um acoplamento muito ruim pois está escondido
- Uma chamada de método pode mudar um valor global e o código não deixa isso aparente
- Um tipo de acoplamento muito ruim
- Acoplamento de dados internos
- Um objeto altera os dados locais de um outro objeto
- Ocorrência comum:
- Friends em C++
- Dados protected ou públicos de java
- Use com cuidado!
Consequências
- Uma classefracamente acoplada não é afetada (ou pouco afetada) por mudanças em outras
classes
- Simples de entender isoladamente
- Reuso mais fácil
Problema
- Como gerenciar a complexidade?
- A coesão mede quão relacionados ou focados estão as responsabilidades da classe
- Também chamado coesão funcional (ver à frente)
- Uma classe com baixa coesão faz muitas coisas não relacionadas e leva aos seguintes
problemas:
- Difícil de entender
- Difícil de reusar
- Difícil de manter
- "Delicada": constantemente sendo afetada por outras mudanças
- Uma classe com baixa coesão assumiu responsabilidades que pertencem a outras classes e
deveriam ser delagadas
Solução
- Atribuir responsabilidades que mantenham alta coesão
Exemplo
- Mesmo exemplo usado para Low Coupling
- Na primeira alternativa, TPDV assumiu uma responsabilidade de efetuar um pagamento
(método façaPagamento())

- Até agora, não há problema
- Mas suponha que o mesmo ocorra com várias outras operações de sistema
- TPDV vai acumular um monte de métodos não muito focados
- Resultado: baixa coesão
- A segunda alternativa delega façaPagamento() para a classe Venda
- Mantém maior coesão em TPDV

Discussão
- Alta coesão é outro princípio de ouro que deve ser sempre mantido em mente durante o
projeto
- Tipos de coesão entre módulos
- Coincidental (pior)
- Lógico
- Temporal
- Procedural
- De comunicação
- Sequencial
- Funcional (melhor)
- Coesão coincidental
- Há nenhuma (ou pouca) relação construtiva entre os elementos de um módulo
- No linguajar OO:
- Um objeto não representa nenhum conceito OO
- Uma coleção de código comumente usado e herdado através de herança (provavelmente
múltipla)
class Angu {
public static int acharPadrão(String texto, String padrão) {
// ...
}
public static int média(Vector números) {
// ...
}
public static outputStream abreArquivo(string nomeArquivo) {
// ...
}
}
class Xpto extends Angu { // quer aproveitar código de Angu
...
}
- Coesão lógica
- Módulo faz um conjunto de funções relacionadas, uma das quais é escolhida através
de um parâmetro ao chamar o módulo
- Semelhante a acoplamento de controle
- Cura: quebrar em métodos diferentes
public void faça(int flag) {
switch(flag) {
case ON:
// coisas para tratar de ON
break;
case OFF:
// coisas para tratar de OFF
break;
case FECHAR:
// coisas para tratar de FECHAR
break;
case COR:
// coisas para tratar de COR
break;
}
}
- Coesão temporal
- Elementos estão agrupados no mesmo módulo porque são processados no mesmo intervalo
de tempo
- Exemplos comuns:
- Método de inicialização que provê valores defaults para um monte de coisas
diferentes
- Método de finalização que limpa as coisas antes de terminar
procedure inicializaDados() {
font = "times";
windowSize = "200,400";
xpto.nome = "desligado";
xpto.tamanho = 12;
xpto.localização = "/usr/local/lib/java";
}
- Cura: depender de construtores e destrutores
class Xpto {
public Xpto() {
this.nome = "desligado";
this.tamanho = 12;
this.localização = "/usr/local/lib/java";
}
}
- Outro exemplo: arquivo de configuração típico
[Macintosh]
EquationWindow=146,171,406,661
SpacingWindow=0,0,0,0
[Spacing]
LineSpacing=150%
MatrixRowSpacing=150%
MatrixColSpacing=100%
SuperscriptHeight=45%
SubscriptDepth=25%
LimHeight=25%
LimDepth=100%
LimLineSpacing=100%
NumerHeight=35%
DenomDepth=100%
FractBarOver=1pt
FractBarThick=0.5pt
SubFractBarThick=0.25pt
FenceOver=1pt
SpacingFactor=100%
MinGap=8%
RadicalGap=2pt
EmbellGap=1.5pt
PrimeHeight=45%
[General]
Zoom=200
CustomZoom=150
ShowAll=0
Version=2.01
OptimalPrinter=1
MinRect=0
ForceOpen=0
ToolbarDocked=1
ToolbarShown=1
ToolbarDockPos=1
[Fonts]
Text=Times
Function=Times
Variable=Times,I
LCGreek=Symbol,I
UCGreek=Symbol
Symbol=Symbol
Vector=Times,B
Number=Times
[Sizes]
Full=12pt
Script=7pt
ScriptScript=5pt
Symbol=18pt
SubSymbol=12pt
- Coesão procedural
- Associa elementos de acordo com seus relacionamentos procedurais ou algorítmicos
- Um módulo procedural depende muito da aplicação sendo tratada
- Junto com a aplicação, o módulo parece razoável
- Sem este contexto, o módulo parece estranho e muito difícil de entender
- "O que está acontecendo aqui!!!????!!"
- Não pode entender o módulo sem entender o programa e as condições que existem quando
o módulo é chamado
- Cura: reprojete o sistema
- Coesão de comunicação
- Todas as operações de um módulo operam no mesmo conjunto de dados e/ou produzem o
mesmo tipo de dado de saída
- Cura: isole cada elemento num módulo separado
- "Não deveria" ocorrer em sistemas OO usando polimorfismo (classes diferentes
para fazer tratamentos diferentes nos dados)
- Coesão sequencial
- A saída de um elemento de um módulo serve de entrada para o próximo elemento
- Cura: decompor em módulos menores
- Coesão funcional (a melhor)
- Um módulo tem coesão funcional se as operações do módulo puderem ser descritas numa
única frase de forma coerente
- Num sistema OO:
- Cada operação na interface pública do objeto deve ser funcionalmente coesa
- Cada objeto deve representar um único conceito coeso
- Exemplo: um objeto que esconde algum conceito ou estrutura de dados ou recurso e onde
todos os métodos são relacionados por um conceito ou estrutura de dados ou recurso
- Meyer chama isso de "information-strength module"
Consequências
- Melhor claridade e facilidade de compreensão do projeto
- Simplificação da manutenção
- Frequentemente vai mão na mão com acoplamento fraco
- Com granularidade baixa e funcionalidade bem focada, aumenta o reuso
Problema
- Quem deveria receber a responsabilidade de tratar eventos do sistema?
- Um evento do sistema é um evento de alto nível gerado por
um ator externo
- Estão associados a operações do sistema que já vimos
nos Diagramas de Sequência do Sistema
- Exemplo do estudo de caso: Caixa pressiona "Fim de venda"
Solução
- Use um controlador
- Um controlador é um objeto que não é de interface GUI responsável pelo tratamento de
eventos do sistema
- Um controlador define métodos para as operações do sistema
- Atribuir a responsabilidade pelo tratamento de eventos do sistema
a uma classe de acordo com uma das alternativas abaixo:
- Representa o "sistema" como um todo (facade controller)
- Representa o negócio ou organização como um todo (facade
controller)
- Representa algo no mundo real que é ativo (por exemplo, o papel
de uma pessoa) que poderia estar envolvido na tarefa (role controller)
- Representa um handler artificial de todos os eventos do sistema
para um Use Case particular, normalmente chamado
"<NomeDoUseCase>Handler" (use case controller)
Exemplo
- No estudo de caso, há várias operações de sistema:

- Quem deveria ser o controlador para os eventos do sistema?

- Pelo padrão Controller, temos as seguintes alternativas:
- Representa o "sistema": TPDV
- Representa o negócio ou organização: Loja
- Representa algo no mundo real ...: Caixa
- Representa um handler artificial ...: CompraItemHandler
- A escolha particular depende de fatores discutidos na seção Discussão
- Por exemplo, se fosse TPDV, teríamos:

Discussão
- De forma geral, o mesmo controlador deve ser usado para todas as operações de um mesmo
Use Case de forma a manter a informação de estado do Use Case
- A informação de estado pode ser útil para detectar sequências erradas de eventos de
sistema
- Exemplo: façaPagamento() antes de fimDeVenda()
- Não coloque toda a inteligência no controlador
- Delegue para outros objetos,para manter coesão
- Use um Handler artificial quando as outras alternativas exibem acoplamento alto ou
coesão baixa
- Quando está surgindo um "God class"
- Usado em sistemas mais complexos
- Observe que classes "Window", "Applet", "Application",
"View", "Document" não devem ser controladores
- Tais classes podem receber o evento e delegá-lo ao controlador
- Não se deve colocar business logic num objeto de interface com o usuário
- Um design correto seria:

- Um design incorreto seria:

- O Role Controller pode levar a um mau projeto
- O fato de algo ser feito por uma pessoa no mundo real não necessariamente significa que
isso é uma boa alternativa em software
- É mais comum "dar vida aos objetos" (não animados)
Consequências
- Maior possibilidade de reuso, já que o business logic não está nos objetos de
interface
- Exemplo: embutir o business logic num objeto de interface não permitiria fazer EAI
(Enterprise Application Integration)
- Ajuda a verificar o sequenciamento das operações do sistema, através do estado do
controlador
Responsabilidades, Role Playing e Cartões CRC
- Embora não faça parte de UML, uma técnica chamada Cartões CRC
é muito usada para atribuir responsabilidades durante o projeto
- CRC = Class-Responsibility-Collaborations
- CRC cards inventadas por Ward Cunningham e Kent Beck (Tektronix)
Cartão CRC é um cartão pequeno (para só escrever o essencial) para cada classe
- Escreve-se o nome da classe, suas responsabilidades e colaborações
- Só pense nas responsabilidades de alto nível

- São desenvolvidos em pequenos grupos em que cada pessoa assume o papel (Role) de uma ou
mais classes
- Mais detalhes aqui:
- Designing Object-Oriented Software, Wirfs-Brock, Wilkerson e Wiener; Prentice Hall,
1990.
- Algumas pessoas acham que é melhor usar ferramentas gráficas em vez de CRC
proj1-5 programa anterior
próxima