Record Set

Паттерн проектирования 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())
}

Использована иллюстрация с сайта Мартина Фаулера.

Источник