├── LICENSE ├── README.md ├── classdiagram.png └── classdiagram_template_method.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Marco Tulio Valente 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aula Prática sobre Refactoring 2 | 3 | **Prof. Marco Tulio Valente** 4 | 5 | Objetivo: colocar em prática os conceitos de refactoring aprendidos na sala de aula. Para isso, o aluno vai realizar alguns refactorings em um sistema hipotético, também usado nos capítulos iniciais do livro do Fowler, que é o livro clássico sobre o tema. 6 | 7 | Para tirar proveito do exercício: 8 | 9 | * **É importante não apenas seguir o roteiro mecanicamente, mas também analisar os benefícios alcançados com cada refactoring**. 10 | * Ou seja, pense sempre nos motivos que tornam o código refatorado melhor do que o código anterior. 11 | 12 | Importante também: o roteiro baseia-se em uma parte (incompleta) de um sistema. Logo, você não vai executá-lo. O nosso foco não está no funcionamento do sistema, mas na melhoria de sua estrutura interna (design) após cada passo do roteiro. 13 | 14 | Em caso de dúvida sobre os refactorings propostos, você pode consultar o [Capítulo 9](https://engsoftmoderna.info/cap9.html) do nosso livro. 15 | 16 | O exemplo está em Java, mas a sintaxe é familiar mesmo para aqueles que nunca programaram na linguagem. Infelizmente, não é possível fazer em uma outra linguagem, pois a correção será automática. Você pode usar uma IDE Java (Eclipse, NetBeans, IntelliJ) ou então uma IDE online (como [repl.it](https://replit.com/)). **A vantagem de usar o repl.it é que você não precisa realizar nenhuma instalação local na sua máquina e também já dispobilizamos um [exemplo](https://replit.com/@engsoftmoderna/VideoStore) configurado com a versão inicial do sistema usado no roteiro**. 17 | 18 | Instruções: 19 | 20 | * Primeiro, crie um repositório no GitHub. 21 | 22 | * Vá seguindo o roteiro, refactoring a refactoring. 23 | 24 | * Após cada etapa, dê um **COMMIT & PUSH**, adicionado uma descrição (mensagem) no commit conforme as instruções do roteiro. Esses commits serão usados na correção, para garantir que você realizou todos os refactorings solicitados. 25 | 26 | 27 | Observações: 28 | 29 | * Os código e refactorings realizados nesta aula prática podem ser usados em nossas aulas e artigos futuros. Evidentemente, sempre de forma anônima (isto é, sem qualquer possibilidade de identificar o aluno ou mesmo a sua turma). Caso você não se sinta à vontade com isso, basta nos enviar um e-mail, solicitando que seu código não seja usado futuramente. 30 | 31 | * **Códigos que não compilam -- em qualquer um dos refactorings e passos -- serão avaliados com nota zero pelo nosso sistema de correção automática** 32 | 33 | 34 | # Versão Inicial 35 | 36 | As classes que vamos usar fazem parte de um sistema de video-locadora, para aluguel de vídeos. 37 | 38 | Inicialmente, são três classes: `Movie` (filmes que podem ser alugados), `Rental` (dados de um aluguel) e `Customer` (clientes da locadora). 39 | 40 | Se estiver usando o repl.it, já disponibilizamos esse código inicial pronto: veja [aqui](https://replit.com/@engsoftmoderna/VideoStore). 41 | 42 | Se não estiver usando repl.it siga os passos a seguir na sua IDE ou editor favoritos. 43 | 44 | * Copie o código da classe `Movie` para um arquivo chamado `Movie.java`: 45 | ```java 46 | public class Movie { 47 | 48 | public static final int CHILDRENS = 2; 49 | public static final int REGULAR = 0; 50 | public static final int NEW_RELEASE = 1; 51 | 52 | private String _title; 53 | private int _priceCode; 54 | 55 | public Movie(String title, int priceCode) { 56 | _title = title; 57 | _priceCode = priceCode; 58 | } 59 | 60 | public int getPriceCode() { 61 | return _priceCode; 62 | } 63 | 64 | public void setPriceCode(int arg) { 65 | _priceCode = arg; 66 | } 67 | 68 | public String getTitle (){ 69 | return _title; 70 | } 71 | } 72 | ``` 73 | 74 | * Copie o código da classe `Rental` para um arquivo chamado `Rental.java`: 75 | 76 | ```java 77 | public class Rental { 78 | 79 | private Movie _movie; 80 | private int _daysRented; 81 | 82 | public Rental(Movie movie, int daysRented) { 83 | _movie = movie; 84 | _daysRented = daysRented; 85 | } 86 | 87 | public int getDaysRented() { 88 | return _daysRented; 89 | } 90 | 91 | public Movie getMovie() { 92 | return _movie; 93 | } 94 | } 95 | ``` 96 | 97 | * Copie o código da classe `Customer` para um arquivo chamado `Customer.java`: 98 | 99 | ```java 100 | import java.util.Enumeration; 101 | import java.util.Vector; 102 | 103 | public class Customer { 104 | private String _name; 105 | private Vector _rentals = new Vector(); 106 | 107 | public Customer (String name){ 108 | _name = name; 109 | } 110 | 111 | public void addRental(Rental arg) { 112 | _rentals.addElement(arg); 113 | } 114 | 115 | public String getName (){ 116 | return _name; 117 | } 118 | 119 | public String statement() { 120 | double totalAmount = 0; 121 | int frequentRenterPoints = 0; 122 | Enumeration rentals = _rentals.elements(); 123 | String result = "Rental Record for " + getName() + "\n"; 124 | while (rentals.hasMoreElements()) { 125 | double thisAmount = 0; 126 | Rental each = (Rental) rentals.nextElement(); 127 | 128 | //determine amounts for each line 129 | switch (each.getMovie().getPriceCode()) { 130 | case Movie.REGULAR: 131 | thisAmount += 2; 132 | if (each.getDaysRented() > 2) 133 | thisAmount += (each.getDaysRented() - 2) * 1.5; 134 | break; 135 | case Movie.NEW_RELEASE: 136 | thisAmount += each.getDaysRented() * 3; 137 | break; 138 | case Movie.CHILDRENS: 139 | thisAmount += 1.5; 140 | if (each.getDaysRented() > 3) 141 | thisAmount += (each.getDaysRented() - 3) * 1.5; 142 | break; 143 | } 144 | 145 | // add frequent renter points 146 | frequentRenterPoints ++; 147 | // add bonus for a two day new release rental 148 | if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && 149 | each.getDaysRented() > 1) frequentRenterPoints ++; 150 | 151 | //show figures for this rental 152 | result += "\t" + each.getMovie().getTitle()+ "\t" + 153 | String.valueOf(thisAmount) + "\n"; 154 | totalAmount += thisAmount; 155 | 156 | } 157 | //add footer lines 158 | result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; 159 | result += "You earned " + String.valueOf(frequentRenterPoints) + 160 | " frequent renter points"; 161 | return result; 162 | } 163 | } 164 | ``` 165 | 166 | Verifique se existem erros de compilação no seu código. Códigos com erros de compilação serão avaliados com zero. 167 | 168 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 1"**) 169 | 170 | Observação: descrição é a string que descreve a modificação realizada no commit. 171 | 172 | # Refactoring 1: Extract Method 173 | 174 | * **Extrair um método** chamado `amountFor(Rental each)` de `Customer.statement()`; já que esse último é um método maior e que faz muitas coisas. O método extraído vai conter o código relativo ao comentário *determine amounts for each line*, ou seja, você deve extrair o trecho de código referente ao `switch-case`. 175 | 176 | * Lembre-se de atualizar a chamada em `statement()` após as modificações: 177 | 178 | ```java 179 | class Customer ... 180 | public String statement() { 181 | ... 182 | thisAmount = amountFor(each); 183 | ... 184 | } 185 | 186 | private double amountFor(Rental each) { 187 | //Adicionar o trecho de código extraído. 188 | } 189 | ``` 190 | Verifique se existem erros de compilação no seu código. 191 | 192 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 2"**) 193 | 194 | # Refactoring 2: Rename 195 | 196 | * **Renomear** o parâmetro de `amountFor` para ter o nome `aRental`, ou seja, você precisa modificar: 197 | 198 | ```java 199 | private double amountFor(Rental each){ 200 | ... 201 | } 202 | ``` 203 | 204 | Para: 205 | 206 | ```java 207 | private double amountFor(Rental aRental){ 208 | ... 209 | } 210 | ``` 211 | Verifique se existem erros de compilação no seu código. 212 | 213 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 3"**) 214 | 215 | 216 | # Refactoring 3: Move and Rename Method 217 | 218 | * **Mover o método** `amountFor(Rental each)` da classe `Customer` para a classe `Rental`, já que esse método não usa informações da primeira, mas sim da segunda classe. O método deve ser movido com um **novo nome** `getCharge()`. A ideia é que refactorings devem ser feitos em pequenos passos, para garantir que nada está sendo quebrado. 219 | 220 | * Lembre-se de atualizar a chamada em `statement()`, ou seja: 221 | 222 | ```java 223 | class Customer ... 224 | public String statement() { 225 | ... 226 | thisAmount = each.getCharge(); 227 | ... 228 | } 229 | ``` 230 | 231 | ```java 232 | class Rental ... 233 | public double getCharge() { 234 | ... 235 | } 236 | ``` 237 | 238 | **Dica:** Observe que após mover e renomear o método, você deve **remover o parâmetro** `Rental`. Este parâmetro é desnecessário, já que os métodos chamados estão na mesma classe. 239 | 240 | Verifique se existem erros de compilação no seu código. 241 | 242 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 4"**) 243 | 244 | # Refactoring 4: Replace Temp with Query 245 | 246 | Esse refactoring substitui uma variável local e temporária (temp) por uma chamada de função (query). No caso, vamos **substituir** toda referência a `thisAmount` por uma chamada a `each.getCharge()` em `Customer.statement()`. Veja o código após o refactoring: 247 | 248 | ```java 249 | public String statement() { 250 | double totalAmount = 0; 251 | int frequentRenterPoints = 0; 252 | Enumeration rentals = _rentals.elements(); 253 | String result = "Rental Record for " + getName() + "\n"; 254 | while (rentals.hasMoreElements()) { 255 | Rental each = (Rental) rentals.nextElement(); 256 | 257 | // add frequent renter points 258 | frequentRenterPoints ++; 259 | // add bonus for a two day new release rental 260 | if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && 261 | each.getDaysRented() > 1) frequentRenterPoints ++; 262 | 263 | // show figures for this rental 264 | result += "\t" + each.getMovie().getTitle()+ "\t" + String.valueOf 265 | (each.getCharge()) + "\n"; 266 | totalAmount += each.getCharge(); 267 | 268 | } 269 | 270 | // add footer lines 271 | result += "Amount owed is " + String.valueOf(totalAmount) + "\n"; 272 | result += "You earned " + String.valueOf(frequentRenterPoints) 273 | + " frequent renter points"; 274 | return result; 275 | } 276 | ``` 277 | 278 | Resumindo o que foi feito acima: `thisAmount` sumiu e, nos dois pontos em que era usada, substitui-se por uma chamada a `getCharge()`. 279 | 280 | Motivação para esse refactoring (chamado Replace Temp with Query): ficar livre de variáveis temporárias, que tendem a dificultar o entendimento do código; pois você tem que lembrar o que elas armazenam. Claro, pode-se alegar que isso causa um problema de performance. Porém, esse possível problema pode ser inclusive resolvido pelo compilador (isto é, pelas estratégias de otimização de código implementadas pelo compilador de Java). 281 | 282 | Verifique se existem erros de compilação no seu código. 283 | 284 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 5"**) 285 | 286 | # Refactoring 5: Extract and Move Method 287 | 288 | * Vamos decompor mais uma vez `statement()`, para ir diminuindo o seu tamanho e complexidade. Para isso, vamos **extrair um método** chamado `getFrequentRenterPoints()` com o código relativo ao comentário *add frequent renter points*. Você precisa **mover** o método extraído para a classe `Rental`. 289 | 290 | * A variável `frequentRenterPoints` receberá o retorno da função extraída: 291 | 292 | 293 | ```java 294 | class Customer ... 295 | public String statement() { 296 | ... 297 | frequentRenterPoints += each.getFrequentRenterPoints(); 298 | ... 299 | } 300 | ``` 301 | 302 | ```java 303 | class Rental ... 304 | public int getFrequentRenterPoints() { 305 | //Adicionar o trecho de código extraído. 306 | } 307 | ``` 308 | 309 | **Dica:** Você deve extrair a condição que verifica se o filme refere-se a um novo lançamento (NEW_RELEASE). Se a condição for verdadeira, `getFrequentRenterPoints()` retorna 2. Caso contrário, retorna 1, ou seja, sem bônus. 310 | 311 | Verifique se existem erros de compilação no seu código. 312 | 313 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 6"**) 314 | 315 | # Refactoring 6: Replace Temp With Query 316 | 317 | Mais duas variáveis locais (temp) vão ser extraídas para funções (queries) na classe `Customer`. São elas: 318 | 319 | * `totalAmount` vai ser substituída por `getTotalCharge()` 320 | * `frequentRenterPoints` vai ser substituída por `getTotalFrequentRenterPoints()`. 321 | 322 | Veja como deve ficar o código após esses dois refactorings: 323 | 324 | 325 | ```java 326 | class Customer... 327 | 328 | public String statement() { 329 | Enumeration rentals = _rentals.elements(); 330 | String result = "Rental Record for " + getName() + "\n"; 331 | while (rentals.hasMoreElements()) { 332 | Rental each = (Rental) rentals.nextElement(); 333 | 334 | // show figures for this rental 335 | result += "\t" + each.getMovie().getTitle()+ "\t" + 336 | String.valueOf(each.getCharge()) + "\n"; 337 | } 338 | 339 | // add footer lines 340 | result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n"; 341 | result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + 342 | " frequent renter points"; 343 | return result; 344 | } 345 | 346 | private double getTotalCharge() { 347 | double result = 0; 348 | Enumeration rentals = _rentals.elements(); 349 | while (rentals.hasMoreElements()) { 350 | Rental each = (Rental) rentals.nextElement(); 351 | result += each.getCharge(); 352 | } 353 | return result; 354 | } 355 | 356 | private int getTotalFrequentRenterPoints(){ 357 | int result = 0; 358 | Enumeration rentals = _rentals.elements(); 359 | while (rentals.hasMoreElements()) { 360 | Rental each = (Rental) rentals.nextElement(); 361 | result += each.getFrequentRenterPoints(); 362 | } 363 | return result; 364 | } 365 | ``` 366 | 367 | Dois comentários breves, sobre alguns pontos que você já pode estar pensando sobre os últimos refactorings: 368 | 369 | * Eles aumentaram o tamanho do código: porém, também não foi tanto assim ... 370 | * Eles fizeram com o que o loop de `rentals` seja percorrido três vezes; na primeira versão do código, esse loop era executado uma única vez. Isso vai gerar problemas de performance? Talvez sim; mas, provavelmente na maioria dos casos, não vai fazer diferença, pois um cliente não tem tantos filmes alugados. 371 | 372 | Verifique se existem erros de compilação no seu código. 373 | 374 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 7"**) 375 | 376 | # Nova feature: Statement em HTML 377 | 378 | Neste passo, não vamos refatorar, mas introduzir uma nova feature: imprimir o comprovante de aluguel em HTML. 379 | 380 | Para isso, vamos criar um novo método, chamado `htmlstatement` na classe `Customer`: 381 | 382 | ```java 383 | class Customer ... 384 | public String htmlStatement() { 385 | Enumeration rentals = _rentals.elements(); 386 | String result = "

Rentals for " + getName() + "

\n"; 387 | while (rentals.hasMoreElements()) { 388 | Rental each = (Rental) rentals.nextElement(); 389 | // show figures for each rental 390 | result += each.getMovie().getTitle()+ ": " + 391 | String.valueOf(each.getCharge()) + "
\n"; 392 | } 393 | 394 | // add footer lines 395 | result += "

You owe " + String.valueOf(getTotalCharge()) + "

\n"; 396 | result += "On this rental you earned " + 397 | String.valueOf(getTotalFrequentRenterPoints()) + 398 | " frequent renter points

"; 399 | return result; 400 | } 401 | ``` 402 | 403 | Vantagem: conseguimos reusar todos os métodos criados anteriormente, incluindo: `getCharge()`, `getTotalCharge()` e `getTotalFrequentRenterPoints()`. Por isso, a criação do novo método foi bem rápida e não causou duplicação de código (ou uma duplicação pequena, assumindo que ainda existe alguma lógica repetida, com o método `statement`). 404 | 405 | Verifique se existem erros de compilação no seu código. 406 | 407 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 8"**) 408 | 409 | # Refactoring 7: Replace Conditional with Polymorphism 410 | 411 | Nós vamos realizar este refactoring em sete passos. 412 | 413 | ## Passo 1: Extract and Move Method 414 | 415 | * Primeiro, não faz sentido ter um switch que depende de um atributo (`_priceCode`) de uma outra classe (`Movie`). Logo, você deve **extrair e mover** o código de `getCharge()` na classe Rental para um método chamado `getCharge(int daysRented)` na classe Movie. 416 | 417 | * O método antigo agora apenas inclui uma chamada para o método novo: 418 | 419 | ```java 420 | class Rental ... 421 | public double getCharge(){ 422 | return _movie.getCharge(_daysRented); 423 | } 424 | ``` 425 | 426 | ```java 427 | class Movie ... 428 | public double getCharge(int daysRented){ 429 | //Adicionar o trecho de código extraído. 430 | } 431 | ``` 432 | 433 | Verifique se existem erros de compilação no seu código. 434 | 435 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 9"**) 436 | 437 | ## Passo 2: Extract and Move Method 438 | 439 | * Vamos agora também **extrair** o código de `getFrequentRenterPoints()` para `getFrequentRenterPoints(int daysRented)` e **movê-lo** para a classe `Movie`; ou seja, é melhor que métodos que usam informações sobre tipos de filme estejam todos na classe `Movie`. 440 | 441 | * O método antigo agora apenas inclui uma chamada para o método novo: 442 | 443 | 444 | ```java 445 | class Rental ... 446 | public int getFrequentRenterPoints(){ 447 | return _movie.getFrequentRenterPoints(_daysRented); 448 | } 449 | ``` 450 | 451 | ```java 452 | class Movie ... 453 | public int getFrequentRenterPoints(int daysRented){ 454 | //Adicionar o trecho de código extraído. 455 | } 456 | ``` 457 | 458 | Verifique se existem erros de compilação no seu código. 459 | 460 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 10"**) 461 | 462 | ## Passo 3: Herança 463 | 464 | Por fim, herança, como no diagrama abaixo (**errata**: existe um erro no diagrama, que está no livro; onde consta `getCharge`, leia-se `getPriceCode`). 465 | 466 | 467 | ![heranca](https://github.com/mtov/AulaPraticaRefactoring/blob/master/classdiagram.png) 468 | 469 | 470 | * Isto é, copie o código da classe `Price` para um arquivo chamado `Price.java`: 471 | 472 | 473 | ```java 474 | public abstract class Price { 475 | public abstract int getPriceCode(); 476 | } 477 | ``` 478 | 479 | * Copie o código da classe `ChildrensPrice` para um arquivo chamado `ChildrensPrice.java`: 480 | 481 | ```java 482 | public class ChildrensPrice extends Price { 483 | public int getPriceCode() { 484 | return Movie.CHILDRENS; 485 | } 486 | } 487 | ``` 488 | 489 | * Copie o código da classe `NewReleasePrice` para um arquivo chamado `NewReleasePrice.java`: 490 | 491 | ```java 492 | public class NewReleasePrice extends Price { 493 | public int getPriceCode() { 494 | return Movie.NEW_RELEASE; 495 | } 496 | } 497 | ``` 498 | 499 | * Finalmente, copie o código da classe `RegularPrice` para um arquivo chamado `RegularPrice.java`: 500 | 501 | ```java 502 | public class RegularPrice extends Price { 503 | public int getPriceCode() { 504 | return Movie.REGULAR; 505 | } 506 | } 507 | ``` 508 | 509 | Agora, em `Movie`, vamos: 510 | * remover o campo `_priceCode` 511 | * criar um campo `_price` do tipo `Price` 512 | * alterar o construtor, para chamar `_price.setPriceCode` 513 | * atualizar os métodos `getPriceCode` e `setPriceCode` 514 | * remover o campo `_priceCode`, criar um campo `_price` do tipo `Price`, alterar o construtor, e atualizar os métodos `getPriceCode` e `setPriceCode`: 515 | 516 | ```java 517 | class Movie... 518 | 519 | private Price _price; 520 | 521 | public Movie(String name, int priceCode) { 522 | _title = name; 523 | setPriceCode(priceCode); 524 | } 525 | 526 | public int getPriceCode() { 527 | return _price.getPriceCode(); 528 | } 529 | 530 | public void setPriceCode(int arg) { 531 | switch (arg) { 532 | case REGULAR: 533 | _price = new RegularPrice(); 534 | break; 535 | case CHILDRENS: 536 | _price = new ChildrensPrice(); 537 | break; 538 | case NEW_RELEASE: 539 | _price = new NewReleasePrice(); 540 | break; 541 | default: 542 | throw new IllegalArgumentException("Incorrect Price Code"); 543 | } 544 | } 545 | ... 546 | ``` 547 | 548 | Verifique se existem erros de compilação no seu código. 549 | 550 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 11"**) 551 | 552 | ## Passo 4: Extract and Move Method 553 | 554 | * Mais um refactoring, agora você precisa **extrair e mover** `getCharge(int)` da classe `Movie` para `getCharge(int)` na classe `Price`. 555 | 556 | * O método antigo agora apenas inclui uma chamada para o método novo: 557 | 558 | ```java 559 | class Movie ... 560 | public double getCharge(int daysRented) { 561 | return _price.getCharge(daysRented); 562 | } 563 | ``` 564 | 565 | ```java 566 | class Price ... 567 | public double getCharge(int daysRented) { 568 | //Adicionar o trecho de código extraído. 569 | } 570 | ``` 571 | 572 | Verifique se existem erros de compilação no seu código. 573 | 574 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 12"**) 575 | 576 | ## Passo 5: Herança 577 | 578 | * Caminhando para o final, vamos decompor `getCharge`, criando métodos específicos nas subclasses de `Price` (veja que na classe `Price`, propriamente dita, `getCharge` vai ficar como um método abstrato): 579 | 580 | ```java 581 | class Price... 582 | public abstract double getCharge(int daysRented); 583 | ``` 584 | 585 | ```java 586 | class ChildrensPrice ... 587 | public double getCharge(int daysRented) { 588 | double result = 1.5; 589 | if (daysRented > 3) 590 | result += (daysRented - 3) * 1.5; 591 | return result; 592 | } 593 | ``` 594 | 595 | ```java 596 | class NewReleasePrice ... 597 | public double getCharge(int daysRented){ 598 | return daysRented * 3; 599 | } 600 | ``` 601 | 602 | ```java 603 | class RegularPrice ... 604 | public double getCharge(int daysRented) { 605 | double result = 2; 606 | if (daysRented > 2) 607 | result += (daysRented - 2) * 1.5; 608 | return result; 609 | } 610 | ``` 611 | 612 | Verifique se existem erros de compilação no seu código. 613 | 614 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 13"**) 615 | 616 | 617 | ## Passo 6: Extract and Move Method 618 | 619 | * E agora vamos fazer algo bem parecido com o método `getFrequentRenterPoints(int)`. Para isso, como um primeiro passo, ainda intermediário, você precisa **extrair e mover** o código de `getFrequentRenterPoints(int)` da classe `Movie` para o método `getFrequentRenterPoints(int)` em `Price`. 620 | 621 | * O método antigo agora apenas inclui uma chamada para o método novo: 622 | 623 | ```java 624 | class Movie ... 625 | public int getFrequentRenterPoints(int daysRented) { 626 | return _price.getFrequentRenterPoints(daysRented); 627 | } 628 | ``` 629 | 630 | ```java 631 | class Price ... 632 | public int getFrequentRenterPoints(int daysRented) { 633 | //Adicionar o trecho de código extraído. 634 | } 635 | ``` 636 | 637 | Verifique se existem erros de compilação no seu código. 638 | 639 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 14"**) 640 | 641 | 642 | ## Passo 7: Herança 643 | 644 | * E agora vamos decompor `getFrequentRenterPoints`; ele vai ficar com uma versão "genérica e concreta" em `Price` e, outra, para tratar um caso especial, em `NewReleasePrice`: 645 | 646 | ```java 647 | class Price... 648 | public int getFrequentRenterPoints(int daysRented) { 649 | return 1; 650 | } 651 | ``` 652 | 653 | ```java 654 | class NewReleasePrice 655 | public int getFrequentRenterPoints(int daysRented) { 656 | return (daysRented > 1) ? 2: 1; 657 | } 658 | ``` 659 | 660 | Verifique se existem erros de compilação no seu código. 661 | 662 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 15"**) 663 | 664 | 665 | # Refactoring 8: Template Method 666 | 667 | A classe `Customer` possui dois métodos para imprimir informações sobre a dívida dos clientes. 668 | 669 | O método `statement()` imprime os dados em formato ASCII e o método `htmlStatement()` em formato HTML: 670 | 671 | ```java 672 | class Customer... 673 | public String statement() { 674 | ... 675 | } 676 | 677 | public String htmlStatement() { 678 | ... 679 | } 680 | ``` 681 | 682 | Podemos observar que os métodos acima possuem trechos de código similares. Este código seria duplicado novamente para adicionarmos um novo formato (por exemplo, teríamos um novo método para impressão em formato XML). 683 | 684 | Podemos refatorar estes métodos para usar o padrão **Template Method**. Dessa forma, evitamos a duplicação de código para cada tipo de impressão. Este refactoring será realizado em dois passos. 685 | 686 | ## Passo 1: Herança 687 | 688 | Inicialmente, precisamos organizar estes métodos em duas classes com uma superclasse em comum, como no diagrama abaixo: 689 | 690 | 691 | 692 | 693 | * Isto é, copie o código da classe `Statement` para um arquivo chamado `Statement.java`: 694 | 695 | ```java 696 | public class Statement { 697 | 698 | } 699 | ``` 700 | 701 | * Copie o código da subclasse `TextStatement` para um arquivo chamado `TextStatement.java`: 702 | 703 | ```java 704 | public class TextStatement extends Statement { 705 | 706 | } 707 | ``` 708 | 709 | * Copie o código da subclasse `HtmlStatement` para um arquivo chamado `HtmlStatement.java`: 710 | 711 | ```java 712 | public class HtmlStatement extends Statement { 713 | 714 | } 715 | ``` 716 | 717 | * Em seguida, você deve **extrair e mover** o código de `statement()` para um método chamado `value` na classe `TextStatement`. O método antigo agora apenas inclui uma chamada para o método novo: 718 | 719 | ```java 720 | class Customer... 721 | public String statement() { 722 | return new TextStatement().value(this); 723 | } 724 | ``` 725 | 726 | ```java 727 | import java.util.Enumeration; 728 | 729 | class TextStatement... 730 | public String value(Customer aCustomer) { 731 | Enumeration rentals = aCustomer.getRentals(); 732 | String result = "Rental Record for " + aCustomer.getName() + 733 | "\n"; 734 | while (rentals.hasMoreElements()) { 735 | Rental each = (Rental) rentals.nextElement(); 736 | //show figures for this rental 737 | result += "\t" + each.getMovie().getTitle()+ "\t" + 738 | String.valueOf(each.getCharge()) + "\n"; 739 | } 740 | //add footer lines 741 | result += "Amount owed is " + 742 | String.valueOf(aCustomer.getTotalCharge()) + "\n"; 743 | result += "You earned " + 744 | String.valueOf(aCustomer.getTotalFrequentRenterPoints()) + 745 | " frequent renter points"; 746 | return result; 747 | } 748 | 749 | ``` 750 | 751 | * Precisamos tornar os métodos `getTotalFrequentRenterPoints()` e `getTotalCharge()` públicos na classe `Customer` para acessá-los em `TextStatement`. Além disso, precisamos adicionar `getRentals()` 752 | 753 | ```java 754 | class Customer ... 755 | 756 | public Enumeration getRentals() { 757 | return _rentals.elements(); 758 | } 759 | 760 | public double getTotalCharge() { 761 | ... 762 | } 763 | 764 | public int getTotalFrequentRenterPoints(){ 765 | ... 766 | } 767 | ``` 768 | 769 | * Da mesma forma, você deve **extrair e mover** o código de `htmlStatement()` para um método chamado `value` na classe `HtmlStatement`. O método antigo agora apenas inclui uma chamada para o método novo: 770 | 771 | ```java 772 | class Customer... 773 | public String htmlStatement() { 774 | return new HtmlStatement().value(this); 775 | } 776 | ``` 777 | 778 | ```java 779 | import java.util.Enumeration; 780 | 781 | class HtmlStatement... 782 | public String value(Customer aCustomer) { 783 | Enumeration rentals = aCustomer.getRentals(); 784 | String result = "

Rentals for " + aCustomer.getName() + 785 | "

\n"; 786 | while (rentals.hasMoreElements()) { 787 | Rental each = (Rental) rentals.nextElement(); 788 | //show figures for each rental 789 | result += each.getMovie().getTitle()+ ": " + 790 | String.valueOf(each.getCharge()) + "
\n"; 791 | } 792 | //add footer lines 793 | result += "

You owe " + 794 | String.valueOf(aCustomer.getTotalCharge()) + "

\n"; 795 | result += "On this rental you earned " + 796 | String.valueOf(aCustomer.getTotalFrequentRenterPoints()) + 797 | " frequent renter points

"; 798 | return result; 799 | } 800 | ``` 801 | 802 | Agora, nós temos dois métodos similares nas respectivas subclasses. 803 | 804 | Verifique se existem erros de compilação no seu código. 805 | 806 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 16"**) 807 | 808 | 809 | ## Passo 2: Template Method 810 | 811 | Neste passo, nós temos dois métodos similares nas subclasses. Portanto, podemos usar o padrão **Template Method**. Siga as instruções abaixo para criar o método template, bem como os métodos abstratos que ele chama, na superclasse `Statement`. 812 | 813 | 814 | ```java 815 | class TextStatement... 816 | public String value(Customer aCustomer) { 817 | ... 818 | } 819 | ``` 820 | 821 | ```java 822 | class HtmlStatement... 823 | public String value(Customer aCustomer) { 824 | ... 825 | } 826 | ``` 827 | 828 | ### Template Method - Parte 1 829 | Observe os dois métodos `value` e aplique todas as refatorações necessárias para remover os trechos de código que são diferentes. O objetivo é **tornar estes dois métodos iguais**. 830 | 831 | Verifique se existem erros de compilação no seu código. 832 | 833 | **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 17"**) 834 | 835 | ### Template Method - Parte 2 836 | 837 | Nesta última etapa do exercício, temos dois métodos `value` idênticos nas subclasses `TextStatement` e `HtmlStatement`. Aplique uma última refatoração para remover este código duplicado. Ou seja, `value` será um template. 838 | 839 | 840 | Ao final, verifique se não existem erros de compilação. 841 | 842 | Pronto, com isso terminamos: **COMMIT & PUSH** (Adicione a seguinte descrição nesse commit → **"Commit 18"**) 843 | 844 | 845 | -------------------------------------------------------------------------------- /classdiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtov/AulaPraticaRefactoring/7eabf153544b24c53fcf0fdb46e5bc1b5cbadaee/classdiagram.png -------------------------------------------------------------------------------- /classdiagram_template_method.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtov/AulaPraticaRefactoring/7eabf153544b24c53fcf0fdb46e5bc1b5cbadaee/classdiagram_template_method.png --------------------------------------------------------------------------------