我被指派开发一个帮助Grails应用程序的库。 Grails应用程序有大量的域对象(大约100多个表)。我不希望我的库依赖于Grails应用程序,这使得我的库依赖于db并且很难测试(Grails需要很长时间才能开始)。
例如,Grails应用程序有一个Payment
域对象,它包含很多字段,并且依赖于很多其他域对象。
我只想要一些字段,而不是所有字段或其他依赖域对象。
我是groovy的新手,知道在常规中有Duck Type。我认为定义 Duck接口应该没问题,我不需要修改Grails Payment
对象。
所以,我定义了:
interface IPayment {
String getReceiver()
String getContactPhone()
String getContactEmail()
String getUserIp()
...
}
定义一个接受此IPayment
接口的方法。
但是当我将Payment
对象传递给方法时,编译器会抱怨Payment没有实现IPayment ...
是的,我可以强制Payment implements IPayment
,但这不是我想要的。
我希望grails应用程序只导入我的库,将Payment
对象传递给我的方法并运行。
还有其他设计技巧吗?
感谢。
更新:groovy 1.8.8,抱歉,没有特质。
答案 0 :(得分:4)
您可以使用as
运算符强制将Payment
强制转换为IPayment
。
interface IPayment {
String getReceiver()
String getUserIp()
}
class Payment {
String receiver
String userIp
}
def account(IPayment payment) {
"account: ${payment.receiver}, ${payment.userIp}"
}
def payment = new Payment(
receiver: 'my receiver',
userIp: '127.0.0.1')
assert account(payment as IPayment) == 'account: my receiver, 127.0.0.1'
纯鸭子打字是动态的,并不关心类型:
def duckAccount(payment) {
"duck account: ${payment.receiver}, ${payment.userIp}"
}
assert duckAccount(payment) == "duck account: my receiver, 127.0.0.1"
请注意,as
运算符产生的对象没有强制协议,如果强制类型没有实现正确的方法,则可能在运行时失败:
interface IPayment {
String getReceiver()
String getUserIp()
String getPhone()
}
class Payment {
String receiver
String userIp
}
def account(IPayment payment) {
"account: $payment.receiver, $payment.userIp, $payment.phone"
}
def payment = new Payment(
receiver: 'my receiver',
userIp: '127.0.1.1'
)
try {
assert account(payment as IPayment) ==
"account: my receiver, 127.0.1.1, null"
assert false
}
catch (e) {
assert e.toString().contains(
"MissingMethodException: No signature of method: Payment.getPhone()")
}
答案 1 :(得分:3)
以下是如何独立于Grails应用程序创建库,并通过使用适配器将应用程序与库进行网格化来保持这种方式。适配器欺骗库以为它正在使用预期的Payment
接口。
这是图书馆的一个例子。
interface Payment {
String getReceiver()
String getContactPhone()
String getContactEmail()
String getUserIp()
}
class PaymentProcessor {
def process(Payment payment) {
payment.with {
println receiver
println contactPhone
println contactEmail
println userIp
}
}
}
有Payment
接口和使用它的类。
示例应用程序拥有自己的支付类,它与图书馆预期的支付类略有不同。
class AppPayment {
String receiver
Contact contact
String userIpAddress
}
class Contact {
String phone
String email
}
接收者属性是相同的,但联系信息在不同的类中,并且ip地址属性的命名方式不同。
为了可以在库中使用AppPayment
个实例,您可以创建特定于应用程序的适配器。
trait PaymentAdapter implements Payment {
String getContactPhone() { contact.phone }
String getContactEmail() { contact.email }
String getUserIp() { userIpAddress }
}
通常将适配器实现为类。但是使用Groovy特性会有一些优势。第一个是你不需要实现getReceiver();将使用AppPayment
中已有的等效属性。您只需要实现与Payment
接口不同的任何内容。
有许多方法可以使用适配器。最明确的形式是强制。
def processor = new PaymentProcessor()
def payment = new AppPayment(
receiver: 'John',
contact: new Contact(phone: '1234567890', email: 'john@doe.com') ,
userIpAddress: '192.168.1.101')
processor.process payment as PaymentAdapter
在这种情况下,AppPayment
通过在运行时应用特征而被强制转换为PaymentAdapter
。由于PaymentAdapter
实施付款,PaymentProcessor.process()
接受付款。
您可以在Groovy类别中处理强制,以避免必须直接使用as关键字。
class PaymentAdapterCategory {
static Object process(PaymentProcessor processor, AppPayment payment) {
processor.process payment as PaymentAdapter
}
}
use(PaymentAdapterCategory) {
processor.process payment
}
使用该类别,您可以避免必须明确强制使用适配器;只要您在PaymentProcessor.process()
关闭中调用Object.use(category, closure)
。
由于适配器是特征,并且您可以访问应用程序的源代码,因此可以修改AppPayment
类以实现PaymentAdapter
特征。这样您就可以直接在AppPayment
使用PaymentProcessor.process()
实例。免责声明:这是我最喜欢的选择;我只是觉得它很...... Groovy。
class AppPayment implements PaymentAdapter {
String receiver
Contact contact
String userIpAddress
}
def payment = new AppPayment(...)
processor.process payment
我希望这会有所帮助:)
虽然在大多数情况下这不是问题,但我想告诉您运行时强制进程会更改实例的类。例如:println ((payment as PaymentAdapter).class.name)
打印出AppPayment10_groovyProxy
除非您执行以下操作,否则这不是问题:
def payment = new AppPayment(
receiver: 'John',
contact: new Contact(phone: '1234567890', email: 'john@doe.com') ,
userIpAddress: '192.168.1.101') as PaymentAdapter
// I'm going to barf!!!
something.iExpectAnInstanceOfAppPayment(payment)
编译时特征不会发生这种情况。
2.3之前的Groovy版本不支持traits,因此适配器必须是类。您可以从库中创建通用适配器开始。
/*
* Uses duck typing to delegate Payment method
* calls to a delegate
*/
@groovy.transform.TupleConstructor
abstract class AbstractPaymentAdapter implements Payment {
def delegate // Using @Delegate did not work out :(
String getReceiver() { delegate.receiver }
String getContactPhone() { delegate.contactPhone }
String getContactEmail() { delegate.contactEmail }
String getUserIp() { delegate.userIp }
}
AbstractPaymentAdapter
实现Payment
并期望代理也这样做,但是通过duck typing。这意味着子类只需要实现与Payment
接口不同的任何东西。这使得将适配器实现为类几乎,就像实现为特征的适配器一样简洁。
@groovy.transform.InheritConstructors
class PaymentAdapter extends AbstractPaymentAdapter {
String getContactPhone() { delegate.contact.phone }
String getContactEmail() { delegate.contact.email }
String getUserIp() { delegate.userIpAddress }
}
使用适配器很简单:processor.process new PaymentAdapter(payment)
您可以使用前面显示的Groovy类别,但不能使用强制。但是,通过在asType()
类中实现AppPayment
,可以伪造强制并实现相同的语法。
class AppPayment {
String receiver
Contact contact
String userIpAddress
def asType(Class type) {
type == Payment ? new PaymentAdapter(this) : super.asType(type)
}
}
然后你可以这样做:
processor.process payment as Payment