1. Trang chủ
  2. » Công Nghệ Thông Tin

Programming Groovy dynamic productivity for the java developer phần 10 pptx

24 336 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 24
Dung lượng 289,5 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Groovy builders see Chapter 17, Groovy Builders, on page 260 are good examples of DSLs.. 18.5 Groovy and DSLs Groovy has a number of key capabilities to help create internal DSLs, includ

Trang 1

TYPES OFDSLS 280

The previous code indicates that the alarm mock should returntrue on

the first call and throw an exception on the second

You can find another good example of a DSL in Grails/GORM For

example, you can specify data constraints on an object’s properties

using the following syntax:

class State

{

String twoLetterCode static constraints = {

twoLetterCode unique: true, blank: false, size: 2 2 }

}

Grails smartly recognizes this fluent and expressive syntax for

express-ing the constraints and generates the validation logic both for the front

end and for the back end

Groovy builders (see Chapter 17, Groovy Builders, on page 260) are

good examples of DSLs They’re fluent and built on context

18.3 Types of DSLs

When designing a DSL, you have to decide between two types—external

and internal

An external DSL defines a new language You have the flexibility to

choose the syntax You then parse the commands in your new language

to take actions When I took my first job, the company asked me to

maintain a DSL that needed extensive use oflexandyacc.4The parsing

was a lot of “fun.” You can use languages such as C++ and Java that

do heavyweight lifting for you with the support of extensive parsing

capabilities and libraries For example, you can use ANTLR to build

DSLs ([Par07])

An internal DSL, also called an embedded DSL, defines a new language

as well but within the syntactical confines of another languages You

don’t use any parsers, but you have to construe the syntax by tactfully

mapping to constructs such as methods and properties in the

underly-ing language The users of your internal DSL might not realize they’re

4 I first thought they asked me to do it because I was good I later understood they don’t

ask a new employee to do stuff because they’re good but because no one else wants to do

it!

Trang 2

DESIGNINGINTERNALDSLS 281

using syntax of a broader language However, creating the internal DSL

takes significant design effort and clever tricks to make the underlying

language work for you

I mentioned Ant and Gant earlier Ant, which uses XML, is an example

of an external DSL Gant, on the other hand, uses Groovy to solve the

same problem and is an example of an internal DSL

18.4 Designing Internal DSLs

Dynamic languages are better suited to designing and implementing

internal DSLs They have good metaprogramming capabilities and

flex-ible syntax, and you can easily load and execute code fragments

Not all dynamic languages are created equal, however

I find it very easy to create DSLs in Ruby, for example It is dynamically

typed, parentheses are optional, the symbol (:) can be used instead of

double quoting strings, and so on The elegance of Ruby heavily favors

creating internal DSLs

Creating internal DSLs in Python can be a bit of a challenge The

sig-nificant whitespace can be a hindrance

Groovy’s dynamic typing and metaprogramming capabilities help a

great deal However, it’s picky about parentheses and does not have

the elegant symbol that Ruby does You will have to work around some

of these restrictions, as you’ll see later

It takes significant time, patience, and effort to design an internal DSL

So, be creative, tactfully work around issues, and be willing to

compro-mise at places to succeed in your design efforts

18.5 Groovy and DSLs

Groovy has a number of key capabilities to help create internal DSLs,

including the following:

• Dynamic and optional typing (Section 4.5, Optional Typing, on

page86)

• The flexibility to load scripts dynamically, manipulate, and

exe-cute (Section11.6, Using Groovy Scripts from Groovy, on page178)

Trang 3

CLOSURES ANDDSLS 282

• Groovy classes are open, thanks to categories and

ExpandoMeta-Class (see Chapter 14, MOP Method Injection and Synthesis, on

page202)

• Closures provide a nice context for execution (Chapter 5, Using

Closures, on page92)

• Operator overloading helps freely define operators (Section 3.6,

Operator Overloading, on page56)

• Builder support (Chapter17, Groovy Builders, on page260)

• Flexible parentheses.5

In the rest of this chapter, you’ll look at examples of creating DSLs in

Groovy using these capabilities

18.6 Closures and DSLs

Theidentity( ) method helps delegate calls within a closure, giving you a

context of execution You can take advantage of this approach to create

your own methods with context and fluency

Let’s revisit the pizza-ordering example Say you want to create a syntax

that flows naturally You don’t want to create an instance of PizzaShop

because that is more of an implementation detail You want the context

to be implicit Let’s take a look at the following code (wait until the next

section to see how you can make this more fluent and context-driven):

printf "Pizza will arrive in %d minutes\n" , time

ThegetPizza( ) method accepts a closure within which you call methods

to order pizza using the instance methods of aPizzaShopclass However,

the instance of that class is implicit The delegate (see Section 5.8,

5 This is useful and annoying at the same time Groovy requires no parentheses for

calling methods that take parameters but insists on having them for methods with no

parameters See Section 18.8 , The Parentheses Limitation and a Workaround, on page 285

for a simple trick to work around this annoyance.

Trang 4

METHODINTERCEPTION ANDDSLS 283

Closure Delegation, on page 107) takes care of routing the methods

to the implicit instance, as you can see in the implementation of the

followinggetPizza( ) method:

The output from executing the call to thegetPizza( ) code is as follows:

Pizza will arrive in 25 minutes

Wait a second, how did you get the timevalue printed in the output?

Because the last statement ingetPizza( ) was a call to the closure,

what-ever it returned,getPizza( ) returned The last statement within the

clo-sure is setCard( ), so its result was returned to the caller This DSL

imposes ordering: thesetCard( ) must be the last method called to order

pizza You can work on improving the interface so the ordering is more

obvious Also, you can replace calls to set methods likesetSize Size.LARGE

with assignment statements likesize = Size.LARGE, if you want

18.7 Method Interception and DSLs

You can implement the DSL for ordering pizza without really using a

PizzaShop class You can do that by purely intercepting method calls

Let’s first start with the code to order pizza (stored in a file named

It hardly looks like code It looks more like a data file However, that’s

pure Groovy code, and you’re going to execute it.6 But before that, you

have to perform a few tricks, er, I mean design your DSL

6 Everything you see in that file, except the strings in double quotes, are either method

names or variable names.

Trang 5

METHODINTERCEPTION ANDDSLS 284

Let’s create a file namedGroovyPizzaDSL.groovyand in it define the

vari-ableslarge,thin, andvisa(other variables likesmall,thick,masterCardcan

be defined at will) Now define a methodacceptOrder( ) that will call into

a closure that will eventually execute your DSL Also implement the

methodMissing( ) method that will be called for any method that does not

exist (pretty much all methods called in your DSL fileorderPizza.dsl)

Download CreatingDSLs/GroovyPizzaDSL.groovy

def large = 'large'

def thin = 'thin'

def visa = 'Visa'

def Olives = 'Olives'

def Onions = 'Onions'

def Bell_Pepper = 'Bell Pepper'

println "Validation and processing performed here for order received:"

orderInfo.each { key, value ->

println "${key} -> ${value.join(', ')}"

}

}

You have to figure out a way to put these two files together and execute

You can do that quite easily (see Section 11.6, Using Groovy Scripts

from Groovy, on page178), as shown next InvokeGroovyShell, load the

previous two scripts, form into a cohesive script, and evaluate

Download CreatingDSLs/GroovyPizzaOrderProcess.groovy

def dslDef = new File( 'GroovyPizzaDSL.groovy' ).text

def dsl = new File( 'orderPizza.dsl' ).text

Trang 6

THEPARENTHESESLIMITATION AND AWORKAROUND 285

The output from the previous code is as follows:

Validation and processing performed here for order received:

As you can see, designing and executing a DSL in Groovy (as in

order-pizza.dsl) is pretty easy if you know how to exploit its MOP capabilities

18.8 The Parentheses Limitation and a Workaround

Let’s leave the pizza example behind and move on to look at a simple

register This section will show how to create a DSL for a simple register,

the device that lets you total amounts Here is the first attempt to create

that:

Download CreatingDSLs/Total.groovy

value = 0

def clear() { value = 0 }

def add(number) { value += number }

def total() { println "Total is $value" }

In this code, you wrote total( ) and clear( ) instead of total and clear,

respectively Let’s drop the parentheses and try to calltotal:

Trang 7

CATEGORIES ANDDSLS 286

Executing the previous code gives the following result:

org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack:

No such property: total for class: Total

Groovy thinks that the call to total refers to a (nonexistent) property

Working with a language to design a DSL is like playing with a

2-year-old: you don’t fight with the kid when he gets cranky; you go along

a little bit So, in this case, tell Groovy that it’s OK and work with it

Simply create the properties it wants:

value = 0

def getClear() { value = 0 }

def add(number) { value += number }

def getTotal() { println "Total is $value" }

You wrote properties with the namestotalandclearby writing the

meth-ods getTotal( ) andgetClear( ) Now, Groovy is quite happy (like the kid)

to play with us, and you can call these properties without parentheses:

Categories allow you to intercept method calls in a controlled fashion.7

You can put that to use in creating a DSL Let’s figure out ways to

implement the following fluent call:2.days.ago.at(4.30)

2is an instance ofInteger, and you know thatdaysis not a property on it

You’ll inject that, using categories, as a property (thegetDays( ) method)

Thedaysis just noise It provides connectivity in the sentence “two days

ago at 4.30.” You can implement the method getDays( ) that accepts

Integerand returns the received instance In the getAgo( ) method (for

the ago property), accept an instance of Integer, and return so many

7 See Section 14.1 , Injecting Methods Using Categories, on page 203

Trang 8

CATEGORIES ANDDSLS 287

days before the current date using the operations on theCalendarclass

Finally, in the at( ) method, set the time on that date to the time given,

and return an instance of Date All this can be used within the use( )

block, as shown in the following code:8

Download CreatingDSLs/DSLUsingCategory.groovy

class DateUtil

{

static int getDays(Integer self) { self }

static Calendar getAgo(Integer self)

def hour = ( int )(time.doubleValue())

def minute = ( int )(Math.round((time.doubleValue() - hour) * 100))

A final concern with the DSL syntax created here is that you used

2.days.ago.at(4.30) It’s more natural to use 4:30 instead of 4.30, so it

would be nice to instead use 2.days.ago.at(4:30) Groovy allows you to

accept aMapas a parameter to methods

8 I’m not performing error checking on the time you provide, so you can send 4.70 if

you’d like instead of 5:10; it’s an undocumented feature Also, you may want to clone the

instance of Calendar given to you and modify the clone to avoid any side effects in other

places where you may use these methods.

Trang 9

CATEGORIES ANDDSLS 288

By defining the parameter of the methodago( ) asMapinstead ofDouble,

you can achieve that, as shown here:

Download CreatingDSLs/DSLUsingCategory2.groovy

class DateUtil

{

static int getDays(Integer self) { self }

static Calendar getAgo(Integer self)

The only restriction in this approach using categories is that you can

use the DSL only within theuse( ) blocks This may not be such a severe

restriction It might actually be good because the method injection is

controlled Once you leave the block of code, the methods injected are

forgotten, which might be desirable In Section 18.10,

ExpandoMeta-Class and DSLs, on the next page, you will see how to implement the

same syntax usingExpandoMetaClass

Trang 10

EXPANDOMETACLASS ANDDSLS 289

18.10 ExpandoMetaClass and DSLs

Categories take effect only within the use blocks, and their effect is

fairly limited in scope If you want the method injection to be effective

throughout your application, you can use theExpandoMetaClassinstead

of categories Let’s use ExpandoMetaClassto implement the DSL syntax

you saw in the previous section:

No signature of method: java.util.GregorianCalendar.at()

is applicable for argument types: (java.util.LinkedHashMap) values: {[4:30]}

The reason for this exception is that the method was added to the

interface Calendar, and by default ExpandoMetaClass does not provide

that to inheriting/implementing classes One solution is to add the

at( ) method to the class GregorianCalendar However, that would be

Trang 11

EXPANDOMETACLASS ANDDSLS 290

wrong in principle since you’re not supposed to know the details of

the implementation while working at the level of interfaces You can

fix this problem by adding one line of code before any other code is

executed Put the following at the top of the code and rerun the code:

ExpandoMetaClass.enableGlobally() (see Section 14.2, Injecting Methods

Using ExpandoMetaClass, on page 208) The output from the previous

code after this change is as follows:

Fri Nov 23 04:30:00 MST 2007

As you learned in this chapter, creating an internal DSL in Groovy is

fairly easy The dynamic nature and optional typing allows you to create

a fluent interface Closures help you create context Groovy’s categories

and ExpandoMetaClass are helpful to inject, intercept, and synthesize

method calls and properties Finally, Groovy’s ability to load and

exe-cute arbitrary scripts comes in handy to exeexe-cute the DSLs

Trang 12

Appendix A

Web Resources

Groovy Home .http://groovy.codehaus.org

Home of the Groovy project for documentation and downloads

Groovy Download Page http://groovy.codehaus.org/Download

Direct link to the Groovy download page for latest released version and previousversions

Groovy Daily Build http://build.canoo.com/groovy

Place to download current builds of Groovy project, if you like to stay on thebleeding edge

Groovy API Javadoc http://groovy.codehaus.org/api

Javadoc for the Groovy API

The GDK http://groovy.codehaus.org/groovy-jdk

List of the methods that are part of the Groovy JDK—Groovy extensions to theJDK

Markmail for Groovy Mailing List http://groovy.markmail.org

Convenient place to search for any topics discussed in the Groovy users mailinglist

Groovy Mailing Lists http://groovy.codehaus.org/Mailing+Lists

List and details of Groovy mailing lists

A Bit of Groovy History http://glaforge.free.fr/weblog/index.php?itemid=99

A blog by Guillaume Laforge on Groovy history

MetaClassand Method Interception .

.http://graemerocher.blogspot.com/2007/06/dynamic-groovy-groovys-equivalent-to html

A blog by Graeme Rocher on Groovy’s metaprogramming capabilities and openclasses

Ngày đăng: 12/08/2014, 23:22

TỪ KHÓA LIÊN QUAN