Ниже показано, как вы можете создать свою библиотеку независимо от своего приложения 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: '[email protected]') ,
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()
. ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: это мой любимый вариант; Я просто думаю, что это довольно... Круто.
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: '[email protected]') ,
userIpAddress: '192.168.1.101') as PaymentAdapter
// I'm going to barf!!!
something.iExpectAnInstanceOfAppPayment(payment)
Этого не происходит с трейтами времени компиляции.
Без признаков
Версии Groovy до 2.3 не поддерживают трейты, поэтому адаптер должен быть классом. Вы можете начать с создания универсального адаптера в библиотеке.
/*
* 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
и ожидает, что делегат сделает то же самое, но через утиную печать. Это означает, что подклассы должны реализовывать только то, что отличается от интерфейса 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
person
Emmanuel Rosa
schedule
18.09.2015