DCI (Data Context Interaction) — это новый взгляд на объектно-ориентированное программирование. Если вы хотите прочитать теорию, чтобы увидеть разницу между DCI и традиционным ООП, есть хорошая статья, освещающая эту тему:
И эта презентация тоже может быть очень полезной:
В этом посте я хотел бы показать один простой пример программы DCI, написанной на Groovy.
Использовать DCI в Java непросто, так как по своей природе DCI требует совместного использования классов и Java, и не предоставляет каких-либо достойных способов сделать это. Но Groovy делает — mixins.
Mixin — очень интересный подход, позволяющий разделить часть поведения между несколькими классами. Подход использовался разработчиками Smalltalk более 30 лет, но он стал популярным на «более или менее массовых» языках всего несколько лет назад. Чтобы продемонстрировать, как использовать миксины в Groovy для реализации DCI, я напишу простое приложение:
Требования:
- У нас есть пользователи
- У нас есть компании
- Пользователи могут следить за пользователями
- Пользователи могут следить за компаниями
- Пользователи — это объекты, хранящиеся в базе данных.
- Компании — это лица, хранящиеся в базе данных
По сути, у нас есть два класса домена: пользователи, компании и сценарии использования: когда пользователь начинает следовать за компанией и начинает следовать за другим пользователем.
Во-первых, давайте создадим наши доменные классы:
class User { int id String name int age List followers } class Company { int id String name String country List followers }
И простой класс Database, представляющий постоянную инфраструктуру реального приложения:
class Database { private users = [ 1: new User(id: 1, name: 'Johh', age: 25, followers: []), 2: new User(id: 2, name: 'Piter', age: 25, followers: []) ] private companies = [1: new Company(id: 1, name: 'BigCompany', country: 'Canada', followers: [])] User findUserById(int id){ users[id] } Company findCompanyById(int id){ companies[id] } void updateUser(User user){ users[user.id] = user } void updateCompany(Company company){ companies[company.id] = company } }
Доменные объекты в DCI не являются умными. Они не предоставляют методы для всех возможных вариантов использования. Они не взаимодействуют друг с другом сложным образом. Вместо этого у них есть набор полей и множество удобных методов для доступа к ним.
Вся наша бизнес-логика сосредоточена в ролях. Роль — это часть поведения, которую мы можем смешать с нашими классами домена для решения бизнес-задач. Нам понадобятся две роли для нашего игрушечного приложения:
class Follower { } class Following { void addFollower(follower){ followers << follower } }
Последователь — это маркерная роль. Нет необходимости создавать такую роль, но мне нравится делать это, поскольку это проясняет мои намерения.
Немного мета-программирования Groovy, чтобы улучшить синтаксис добавления новой роли в объект домена:
Object.metaClass.addRole = {role-> delegate.metaClass.mixin role }
Итак, вместо:
object.metaClass.mixin RoleName
это будет выглядеть так:
object.addRole RoleName
Единственная оставленная часть — это контекст, который извлекает доменные объекты из базы данных, назначает им некоторые роли и запускает бизнес-сценарий:
class FollowersListContext {
Database db
void addFollowerToUser(int followingUserId, int followerUserId){
def following = db.findUserById(followingUserId)
def follower = db.findUserById(followerUserId)
following.addRole Following
follower.addRole Follower
following.addFollower follower
db.updateUser following
}
void addFollowerToCompany(int followingCompanyId, int followerUserId){
def following = db.findCompanyById(followingCompanyId)
def follower = db.findUserById(followerUserId)
following.addRole Following
follower.addRole Follower
following.addFollower follower
db.updateCompany following
}
}
Database db = new Database()
def context = new FollowersListContext(db: db)
context.addFollowerToUser(1, 2)
Это может быть не самый впечатляющий пример, поскольку мы разделяем только одну строку кода, но он показывает, как все части работают вместе. В реальных словах пример ролей сделает гораздо больше, чем просто добавление элемента в коллекцию. В результате такого рода декомпозиция позволит нам разбить сложное поведение и избежать классов монстров с тысячами строк кода.
Имея такой мощный язык, как Groovy, нетрудно настроить наш код и сделать его немного лучше. Например, мы можем использовать такой код для назначения ролей:
def following = db.findCompanyById(followingCompanyId).inRole(Following)
Или даже такой код (хотя он выглядит немного странно для меня):
Following.playedBy(company).addFollower Follower.playedBy(user)