Record Set

Паттерн проектирования Record Set
Описание Record Set
Представление данных из таблицы в приложении.
За последние двадцать лет основным способом представления данных в БД стали реляционные таблицы. Почти каждый новый разработчик использует реляционные данные.
На этой почве появилось множество инструментов для быстрого построения UI (пользовательского интерфейса). Эти UI-фреймворки основываются на реляционности данных и предоставляют различные UI-элементы, которые легко настраиваются и управляются практически безо всякого программирования.
Обратная сторона медали в том, что, несмотря на невероятную лёгкость вывода и работы с данными, эти элементы не предусматривают возможности добавления кода бизнес-логики. Проверки типа "правильный ли формат у эта даты" и любые правила исполнения попросту некуда поставить. И в итоге, эта логика либо забивается в БД, либо смешивается в кодом вывода информации.
Суть Record Set в предоставлении структуры данных, которая выглядит в точности как результат SQL-запроса, но может управляться и обрабатываться любыми частями системы.
Примеры реализации
// Record Set Pattern in JavaScript
class RecordSet {
constructor(data = []) {
this.records = data;
this.currentIndex = 0;
}
addRecord(record) {
this.records.push(record);
}
getRecord(index) {
return this.records[index];
}
getCurrentRecord() {
return this.records[this.currentIndex];
}
moveNext() {
if (this.currentIndex < this.records.length - 1) {
this.currentIndex++;
return true;
}
return false;
}
movePrevious() {
if (this.currentIndex > 0) {
this.currentIndex--;
return true;
}
return false;
}
moveFirst() {
this.currentIndex = 0;
}
moveLast() {
this.currentIndex = this.records.length - 1;
}
getCount() {
return this.records.length;
}
isEmpty() {
return this.records.length === 0;
}
filter(predicate) {
const filteredRecords = this.records.filter(predicate);
return new RecordSet(filteredRecords);
}
sort(compareFunction) {
const sortedRecords = [...this.records].sort(compareFunction);
return new RecordSet(sortedRecords);
}
}
// Usage
const recordSet = new RecordSet([
{ id: 1, name: 'John Doe', email: 'john@example.com', age: 30 },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', age: 25 },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', age: 35 }
]);
console.log('Total records:', recordSet.getCount());
console.log('First record:', recordSet.getCurrentRecord());
recordSet.moveNext();
console.log('Second record:', recordSet.getCurrentRecord());
// Filter records
const youngUsers = recordSet.filter(record => record.age < 30);
console.log('Young users:', youngUsers.getCount());
// Sort records
const sortedByAge = recordSet.sort((a, b) => a.age - b.age);
console.log('Sorted by age:', sortedByAge.getCurrentRecord());
<?php
// Record Set Pattern in PHP
class RecordSet {
private $records;
private $currentIndex;
public function __construct($data = []) {
$this->records = $data;
$this->currentIndex = 0;
}
public function addRecord($record) {
$this->records[] = $record;
}
public function getRecord($index) {
return $this->records[$index] ?? null;
}
public function getCurrentRecord() {
return $this->records[$this->currentIndex] ?? null;
}
public function moveNext() {
if ($this->currentIndex < count($this->records) - 1) {
$this->currentIndex++;
return true;
}
return false;
}
public function movePrevious() {
if ($this->currentIndex > 0) {
$this->currentIndex--;
return true;
}
return false;
}
public function moveFirst() {
$this->currentIndex = 0;
}
public function moveLast() {
$this->currentIndex = count($this->records) - 1;
}
public function getCount() {
return count($this->records);
}
public function isEmpty() {
return empty($this->records);
}
public function filter($predicate) {
$filteredRecords = array_filter($this->records, $predicate);
return new RecordSet(array_values($filteredRecords));
}
public function sort($compareFunction) {
$sortedRecords = $this->records;
usort($sortedRecords, $compareFunction);
return new RecordSet($sortedRecords);
}
}
// Usage
$recordSet = new RecordSet([
['id' => 1, 'name' => 'John Doe', 'email' => 'john@example.com', 'age' => 30],
['id' => 2, 'name' => 'Jane Smith', 'email' => 'jane@example.com', 'age' => 25],
['id' => 3, 'name' => 'Bob Johnson', 'email' => 'bob@example.com', 'age' => 35]
]);
echo "Total records: " . $recordSet->getCount() . "\n";
echo "First record: " . json_encode($recordSet->getCurrentRecord()) . "\n";
$recordSet->moveNext();
echo "Second record: " . json_encode($recordSet->getCurrentRecord()) . "\n";
// Filter records
$youngUsers = $recordSet->filter(function($record) {
return $record['age'] < 30;
});
echo "Young users: " . $youngUsers->getCount() . "\n";
// Sort records
$sortedByAge = $recordSet->sort(function($a, $b) {
return $a['age'] - $b['age'];
});
echo "Sorted by age: " . json_encode($sortedByAge->getCurrentRecord()) . "\n";
?>
// Record Set Pattern in Go
package main
import (
"fmt"
"sort"
)
type Record map[string]interface{}
type RecordSet struct {
records []Record
currentIndex int
}
func NewRecordSet(data []Record) *RecordSet {
return &RecordSet{
records: data,
currentIndex: 0,
}
}
func (rs *RecordSet) AddRecord(record Record) {
rs.records = append(rs.records, record)
}
func (rs *RecordSet) GetRecord(index int) Record {
if index >= 0 && index < len(rs.records) {
return rs.records[index]
}
return nil
}
func (rs *RecordSet) GetCurrentRecord() Record {
if rs.currentIndex >= 0 && rs.currentIndex < len(rs.records) {
return rs.records[rs.currentIndex]
}
return nil
}
func (rs *RecordSet) MoveNext() bool {
if rs.currentIndex < len(rs.records)-1 {
rs.currentIndex++
return true
}
return false
}
func (rs *RecordSet) MovePrevious() bool {
if rs.currentIndex > 0 {
rs.currentIndex--
return true
}
return false
}
func (rs *RecordSet) MoveFirst() {
rs.currentIndex = 0
}
func (rs *RecordSet) MoveLast() {
rs.currentIndex = len(rs.records) - 1
}
func (rs *RecordSet) GetCount() int {
return len(rs.records)
}
func (rs *RecordSet) IsEmpty() bool {
return len(rs.records) == 0
}
func (rs *RecordSet) Filter(predicate func(Record) bool) *RecordSet {
var filteredRecords []Record
for _, record := range rs.records {
if predicate(record) {
filteredRecords = append(filteredRecords, record)
}
}
return NewRecordSet(filteredRecords)
}
func (rs *RecordSet) Sort(compareFunc func(Record, Record) bool) *RecordSet {
sortedRecords := make([]Record, len(rs.records))
copy(sortedRecords, rs.records)
sort.Slice(sortedRecords, func(i, j int) bool {
return compareFunc(sortedRecords[i], sortedRecords[j])
})
return NewRecordSet(sortedRecords)
}
// Usage
func main() {
recordSet := NewRecordSet([]Record{
{"id": 1, "name": "John Doe", "email": "john@example.com", "age": 30},
{"id": 2, "name": "Jane Smith", "email": "jane@example.com", "age": 25},
{"id": 3, "name": "Bob Johnson", "email": "bob@example.com", "age": 35},
})
fmt.Printf("Total records: %d\n", recordSet.GetCount())
fmt.Printf("First record: %+v\n", recordSet.GetCurrentRecord())
recordSet.MoveNext()
fmt.Printf("Second record: %+v\n", recordSet.GetCurrentRecord())
// Filter records
youngUsers := recordSet.Filter(func(record Record) bool {
age, ok := record["age"].(int)
return ok && age < 30
})
fmt.Printf("Young users: %d\n", youngUsers.GetCount())
// Sort records
sortedByAge := recordSet.Sort(func(a, b Record) bool {
ageA, _ := a["age"].(int)
ageB, _ := b["age"].(int)
return ageA < ageB
})
fmt.Printf("Sorted by age: %+v\n", sortedByAge.GetCurrentRecord())
}
Использована иллюстрация с сайта Мартина Фаулера.