2014年2月12日 星期三

[ In Action ] Working with closures - Returning & Support for design patterns

Returning from closures: 
So far, you have seen how to declare closures and how to call them. However, there is one crucial topic that we haven’t touched yet: how to return from a closure. In principle, there are two ways of returning: 
1. The last expression of the closure has been evaluated, and the result of this evaluation is returned. This is called end return. Using the return keyword in front of the last expression is optional.
2. The return keyword can also be used to return from the closure prematurely.

This means the following ways of doubling the entries of a list have the very same effect: 
  1. [123].collect{ it * 2 }  
  2. [123].collect{ return it * 2 }  
A premature return can be used to, for example, double only the even entries: 
  1. [123].collect{   
  2.     if (it%2 == 0return it * 2   
  3.     return it  
  4. }  
This behavior of the return keyword inside closures is simple and straightforward. You hardly expect any misconceptions, but there is something to be aware of. 
WARNING: There is a difference between using the return keyword inside and outside of a closure. 
Outside a closure, any occurrence of return leaves the current method. When used inside a closure, it only ends the current evaluation of the closure, which is a much more localized effect. For example, when using List.each , returning early from the closure doesn’t return early from the each method—the closure will still be called again with the next element in the list.

Support for design patterns: 
Design patterns are widely used by developers to enhance the quality of their designs. Each design pattern presents a typical problem that occurs in object-oriented programming along with a corresponding well-tested solution. Let’s take a closer look at the way the availability of closures affects how, which, and when patterns are used. If you’ve never seen design patterns before, we suggest you look at the classic book Design Patterns: Elements of Reusable Object-Oriented Software by Gamma et al, or one of the more recent ones such as Head First Design Patterns by Freeman et al or Refactoring to Patterns by Joshua Kerievsky, or search for “patterns repository” or “patterns catalog” using your favorite search engine. 

Although many design patterns are broadly applicable and apply to any language, some of them are particularly well-suited to solving issues that occur when using programming languages such as C++ and Java. They most often involve implementing new abstractions and new classes to make the original programs more flexible or maintainable. With Groovy, some of the restrictions that face C++ and Java do not apply, and the design patterns are either of less value or more directly supported using language features rather than introducing new classes. We pick two examples to show the difference: the Visitor and Builder patterns. As you’ll see, closures and dynamic typing are the key differentiators in Groovy that facilitate easier pattern usage. 

Relationship to the Visitor pattern 
The Visitor pattern is particularly useful when you wish to perform some complex business functionality on a composite collection (such as a tree or list) of existing simple classes. Rather than altering the existing simple classes to contain the desired business functionality, a Visitor object is introducedThe Visitor knows how to traverse the composite collection and knows how to perform the business functionality for different kinds of a simple class. If the composite changes or the business functionality changes over time, typically only the Visitor class is impacted. 

Listing 5.10 shows how simple the Visitor pattern can look in Groovy; the composite traversal code is in the accept method of the Drawing class, whereas the business functionality (in our case to perform some calculations involving shape area) is contained in two closures, which are passed as parameters to the appropriate acceptmethods. There is no need for a separate Visitor class in this simple case. 
Listing 5.10 The Visitor pattern in Groovy 
  1. class Drawing {  
  2.     List shapes  
  3.     def accept(Closure yield) { shapes.each{it.accept(yield)} }  
  4. }  
  5. class Shape {  
  6.     def accept(Closure yield) { yield(this) }  
  7. }  
  8. class Square extends Shape {  
  9.     def width  
  10.     def area() { width**2 }  
  11. }  
  12. class Circle extends Shape {  
  13.     def radius  
  14.     def area() { Math.PI * radius**2 }  
  15. }  
  16. def picture = new Drawing(shapes:  
  17.     [new Square(width:1), new Circle(radius:1)] )  
  18. def total = 0  
  19. picture.accept { total += it.area() }  
  20. println "The shapes in this drawing cover an area of $total units."  
  21. println 'The individual contributions are: '  
  22. picture.accept { println it.class.name + ":" + it.area() }  
Note. 
上面的 Shape 類別角色就像是 Visitor pattern 中的 Visitor 類別. 而 Drawing 類別把它想做是具有 Shape 行為的物件的集合, 而這邊使用的是 Composite design pattern. 而在 Shape類別上面定義了一個 accept 方法, 可以傳入一個 Closure 物件. 與 Visitor pattern 相對不同的是這邊的行為是由 Closure 決定, 而不是在 Shape 類別中寫死, 而至於傳入的 Closure 物件接收的參數 (或是要處理的對象) 就是那些具備 Shape 類別行為的物件. 簡單去想就是 Shape 類別有點像是代理者的角色, 負責去呼叫那些具備 Shape 類別行為的物件完成傳入的 Closure 物件要它做的事. 這樣的好處是 "真正要做的事" (商業邏輯是在 execution time 決定, 進而 decouple 了那一堆像是 Square  Circle 物件與商業邏輯的行為!

Relationship to the Builder pattern 
The Builder pattern serves to encapsulate the logic associated with constructing a product from its constituent parts. When using the pattern, you normally create aBuilder class, which contains logic determining what builder methods to call and in which sequence to call them to ensure proper assembly of the product. For each product, you must supply the appropriate logic for each relevant builder method used by the Builder class; each builder method typically returns one of the constituent parts. Coding Java solutions based on the Builder pattern is not hard, but the Java code tends to be cumbersome and verbose and doesn’t highlight the structure of the assembled product. For that reason, the Builder pattern is rarely used in Java; developers instead use unstructured or replicated builder-type logic mixed in with their other code. This is a shame, because the Builder pattern is so powerful. 底下是 Builder Pattern 的典型應用 Maze Builder 使用 Groovy 改寫的 Code: 
  1. // 1) Define Product Interface  
  2. interface Maze{ void paint();}  
  3.   
  4. // 2) Director knows how to build the Product.  
  5. class MazeDirector{  
  6.     def maze  
  7.     char[][] product  
  8.     def builders = [:]  
  9.     def innerLoop = {i->  
  10.         maze[0].size().times{  
  11.             builders[maze[i][it]].call(i,it)  
  12.         }  
  13.     }  
  14.       
  15.     Maze build()  
  16.     {  
  17.         product = new char[maze.size()][maze[0].size()]  
  18.         maze.size().times{  
  19.             innerLoop.call(it)  
  20.         }  
  21.           
  22.         return new Maze(){  
  23.             void paint()  
  24.             {  
  25.                 for(int i=0; i<product.length; i++)  
  26.                 {  
  27.                     for(int j=0; j<product.length; j++)  
  28.                     {  
  29.                         printf "%c", product[i][j]  
  30.                     }  
  31.                     println ""  
  32.                 }  
  33.             }  
  34.         }  
  35.     }  
  36.       
  37.     void assignBuilder(int key, Closure c){  
  38.         c.delegate = this  
  39.         builders[key] = c  
  40.     }  
  41. }  
  42.   
  43. // 3) Prepare the materials to build the Product  
  44. def material =     [ [ 1111111 ],  
  45.                      [ 1000021 ],   
  46.                      [ 1010101 ],  
  47.                      [ 1021011 ],   
  48.                      [ 1101011 ],  
  49.                      [ 1002001 ],   
  50.                      [ 1111111 ] ];  
  51.                    
  52. // 4) Instantiate Direct object and assign builders to it                  
  53. MazeDirector mazeDir = new MazeDirector(maze:material)  
  54. mazeDir.assignBuilder(0) { i, j->  
  55.     product[i][j]=' '  
  56. }  
  57.   
  58. mazeDir.assignBuilder(1) { i, j->      
  59.     product[i][j]='□'  
  60. }  
  61.   
  62. mazeDir.assignBuilder(2) { i, j->  
  63.     product[i][j]='*'  
  64. }  
  65.   
  66. // 5) Use Direct to build the Product  
  67. mazeDir.build().paint()  
Groovy’s builders provide a solution using nested closures to conveniently specify even very complex products. Such a specification is easy to read, because the appearance of the code reflects the product structure. Groovy has built-in library classes based on the Builder pattern that allow you to easily build arbi- 
trarily nested node structures, produce markup like HTML or XML, define GUIs in Swing or other widget toolkits, and even access the wide range of functionality in Ant. You will see lots of examples in chapter 8, and we explain how to write your own builders in section 8.6

Relationship to other patterns 
Almost all patterns are easier to implement in Groovy than in Java. This is often because Groovy supports more lightweight solutions that make the patterns less of a necessity—mostly because of closures and dynamic typing. In addition, when patterns are required, Groovy often makes expressing them more succinct and simpler to set up. 

We discuss a number of patterns in other sections of this book, patterns such as Strategy (see 9.1.1 and 9.1.3), Observer (see 13.2.3), and Command (see 9.1.1) benefit from using closures instead of implementing new classes. Patterns such as Adapter and Decorator (see 7.5.3) benefit from dynamic typing and method lookup. We also briefly discuss patterns such as Template Method (see section 5.2.2), the Value Object pattern (see 3.3.2), the incomplete library class smell (see 7.5.3), MVC (see 8.5.6), and the DTO and DAO patterns (see chapter 10). Just by existing, closures can completely replace the Method Object pattern.

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...