Table Module (Обработчик таблицы)

Паттерн проектирования Table Module

Паттерн проектирования Table Module

Описание Table Module

Одна сущность обрабатывает всю бизнес-логику для всех строк таблице БД или виде.

Один из основополагающих принципов в ООП - сочетание данных и методов обработки этих данных. Традиционный объектно-ориентированный подход основан на объектах с соответствием, как, например, в паттерне Domain Model. Таким образом, если есть класс Employee, люой экземпляр этого класса соответствует конкретному работнику. Эта структура работает хорошо, потому что, имея связь, можно выполнять операции, использователь отношения, и собирать данные о работнике

Одна из проблем в паттерне Domain Model заключается в интерфейсе к БД. Этот подход относится к БД, как к сумашедшей тётушке, запертой на чердаке, с которой никто не хочет говорить. Частенько, приходится сильно постараться, чтобы записать или считать данные из БД, преобразуя их между двумя представлениями.

Паттерн Table Module разделяет логику области определения (домена) на отдельные классы для каждой таблицы в БД и один экземпляр класса содержит различные процедуры, работающие с данными. Основное отличие от Domain Model заключается в том, что если есть несколько заказов, то Domain Model будет создавать для каждого из заказов свой объект, а Table Module будет управлять всем заказами при помощи одного объекта.

Примеры реализации

// Table Module Pattern in JavaScript
class CustomerTableModule {
    constructor(database) {
        this.db = database;
        this.tableName = 'customers';
    }
    
    async getAllCustomers() {
        console.log('Getting all customers');
        return await this.db.query(`SELECT * FROM ${this.tableName}`);
    }
    
    async getCustomerById(id) {
        console.log(`Getting customer ${id}`);
        const result = await this.db.query(`SELECT * FROM ${this.tableName} WHERE id = ?`, [id]);
        return result[0] || null;
    }
    
    async createCustomer(customerData) {
        console.log('Creating new customer');
        const { name, email, creditLimit } = customerData;
        const result = await this.db.query(
            `INSERT INTO ${this.tableName} (name, email, credit_limit) VALUES (?, ?, ?)`,
            [name, email, creditLimit]
        );
        return { id: result.insertId, ...customerData };
    }
    
    async getCustomersWithHighCredit() {
        console.log('Getting customers with high credit limit');
        return await this.db.query(`SELECT * FROM ${this.tableName} WHERE credit_limit > 5000`);
    }
    
    async getCustomerCount() {
        console.log('Getting customer count');
        const result = await this.db.query(`SELECT COUNT(*) as count FROM ${this.tableName}`);
        return result[0].count;
    }
}

class OrderTableModule {
    constructor(database) {
        this.db = database;
        this.tableName = 'orders';
    }
    
    async getAllOrders() {
        console.log('Getting all orders');
        return await this.db.query(`SELECT * FROM ${this.tableName}`);
    }
    
    async getOrdersByCustomer(customerId) {
        console.log(`Getting orders for customer ${customerId}`);
        return await this.db.query(`SELECT * FROM ${this.tableName} WHERE customer_id = ?`, [customerId]);
    }
    
    async createOrder(orderData) {
        console.log('Creating new order');
        const { customerId, total, status } = orderData;
        const result = await this.db.query(
            `INSERT INTO ${this.tableName} (customer_id, total, status, order_date) VALUES (?, ?, ?, NOW())`,
            [customerId, total, status]
        );
        return { id: result.insertId, ...orderData };
    }
    
    async getTotalRevenue() {
        console.log('Calculating total revenue');
        const result = await this.db.query(`SELECT SUM(total) as total FROM ${this.tableName} WHERE status = 'completed'`);
        return result[0].total || 0;
    }
}

// Usage
const customerModule = new CustomerTableModule(database);
const orderModule = new OrderTableModule(database);

const customers = await customerModule.getAllCustomers();
const customer = await customerModule.getCustomerById(1);
const totalRevenue = await orderModule.getTotalRevenue();

console.log(`Total customers: ${customers.length}`);
console.log(`Total revenue: $${totalRevenue}`);
<?php
// Table Module Pattern in PHP
class CustomerTableModule {
    private $db;
    private $tableName = 'customers';
    
    public function __construct($database) {
        $this->db = $database;
    }
    
    public function getAllCustomers() {
        echo "Getting all customers\n";
        $stmt = $this->db->prepare("SELECT * FROM {$this->tableName}");
        $stmt->execute();
        return $stmt->fetchAll();
    }
    
    public function getCustomerById($id) {
        echo "Getting customer $id\n";
        $stmt = $this->db->prepare("SELECT * FROM {$this->tableName} WHERE id = ?");
        $stmt->execute([$id]);
        return $stmt->fetch();
    }
    
    public function createCustomer($customerData) {
        echo "Creating new customer\n";
        $stmt = $this->db->prepare("INSERT INTO {$this->tableName} (name, email, credit_limit) VALUES (?, ?, ?)");
        $stmt->execute([$customerData['name'], $customerData['email'], $customerData['creditLimit']]);
        return array_merge(['id' => $this->db->lastInsertId()], $customerData);
    }
    
    public function getCustomersWithHighCredit() {
        echo "Getting customers with high credit limit\n";
        $stmt = $this->db->prepare("SELECT * FROM {$this->tableName} WHERE credit_limit > 5000");
        $stmt->execute();
        return $stmt->fetchAll();
    }
    
    public function getCustomerCount() {
        echo "Getting customer count\n";
        $stmt = $this->db->prepare("SELECT COUNT(*) as count FROM {$this->tableName}");
        $stmt->execute();
        $result = $stmt->fetch();
        return $result['count'];
    }
}

class OrderTableModule {
    private $db;
    private $tableName = 'orders';
    
    public function __construct($database) {
        $this->db = $database;
    }
    
    public function getAllOrders() {
        echo "Getting all orders\n";
        $stmt = $this->db->prepare("SELECT * FROM {$this->tableName}");
        $stmt->execute();
        return $stmt->fetchAll();
    }
    
    public function getOrdersByCustomer($customerId) {
        echo "Getting orders for customer $customerId\n";
        $stmt = $this->db->prepare("SELECT * FROM {$this->tableName} WHERE customer_id = ?");
        $stmt->execute([$customerId]);
        return $stmt->fetchAll();
    }
    
    public function createOrder($orderData) {
        echo "Creating new order\n";
        $stmt = $this->db->prepare("INSERT INTO {$this->tableName} (customer_id, total, status, order_date) VALUES (?, ?, ?, NOW())");
        $stmt->execute([$orderData['customerId'], $orderData['total'], $orderData['status']]);
        return array_merge(['id' => $this->db->lastInsertId()], $orderData);
    }
    
    public function getTotalRevenue() {
        echo "Calculating total revenue\n";
        $stmt = $this->db->prepare("SELECT SUM(total) as total FROM {$this->tableName} WHERE status = 'completed'");
        $stmt->execute();
        $result = $stmt->fetch();
        return $result['total'] ?? 0;
    }
}

// Usage
$customerModule = new CustomerTableModule($database);
$orderModule = new OrderTableModule($database);

$customers = $customerModule->getAllCustomers();
$customer = $customerModule->getCustomerById(1);
$totalRevenue = $orderModule->getTotalRevenue();

echo "Total customers: " . count($customers) . "\n";
echo "Total revenue: $" . $totalRevenue . "\n";
?>
// Table Module Pattern in Go
package main

import (
    "database/sql"
    "fmt"
)

type CustomerTableModule struct {
    db        *sql.DB
    tableName string
}

func NewCustomerTableModule(db *sql.DB) *CustomerTableModule {
    return &CustomerTableModule{
        db:        db,
        tableName: "customers",
    }
}

func (ctm *CustomerTableModule) GetAllCustomers() ([]map[string]interface{}, error) {
    fmt.Println("Getting all customers")
    rows, err := ctm.db.Query(fmt.Sprintf("SELECT * FROM %s", ctm.tableName))
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var customers []map[string]interface{}
    for rows.Next() {
        var id int
        var name, email string
        var creditLimit float64
        err := rows.Scan(&id, &name, &email, &creditLimit)
        if err != nil {
            return nil, err
        }
        customers = append(customers, map[string]interface{}{
            "id":          id,
            "name":        name,
            "email":       email,
            "creditLimit": creditLimit,
        })
    }
    return customers, nil
}

func (ctm *CustomerTableModule) GetCustomerById(id int) (map[string]interface{}, error) {
    fmt.Printf("Getting customer %d\n", id)
    var name, email string
    var creditLimit float64
    err := ctm.db.QueryRow(fmt.Sprintf("SELECT name, email, credit_limit FROM %s WHERE id = ?", ctm.tableName), id).Scan(&name, &email, &creditLimit)
    if err != nil {
        return nil, err
    }
    return map[string]interface{}{
        "id":          id,
        "name":        name,
        "email":       email,
        "creditLimit": creditLimit,
    }, nil
}

func (ctm *CustomerTableModule) CreateCustomer(customerData map[string]interface{}) (map[string]interface{}, error) {
    fmt.Println("Creating new customer")
    result, err := ctm.db.Exec(fmt.Sprintf("INSERT INTO %s (name, email, credit_limit) VALUES (?, ?, ?)", ctm.tableName),
        customerData["name"], customerData["email"], customerData["creditLimit"])
    if err != nil {
        return nil, err
    }
    
    id, err := result.LastInsertId()
    if err != nil {
        return nil, err
    }
    
    customerData["id"] = int(id)
    return customerData, nil
}

type OrderTableModule struct {
    db        *sql.DB
    tableName string
}

func NewOrderTableModule(db *sql.DB) *OrderTableModule {
    return &OrderTableModule{
        db:        db,
        tableName: "orders",
    }
}

func (otm *OrderTableModule) GetAllOrders() ([]map[string]interface{}, error) {
    fmt.Println("Getting all orders")
    rows, err := otm.db.Query(fmt.Sprintf("SELECT * FROM %s", otm.tableName))
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var orders []map[string]interface{}
    for rows.Next() {
        var id, customerId int
        var total float64
        var status string
        err := rows.Scan(&id, &customerId, &total, &status)
        if err != nil {
            return nil, err
        }
        orders = append(orders, map[string]interface{}{
            "id":         id,
            "customerId": customerId,
            "total":      total,
            "status":     status,
        })
    }
    return orders, nil
}

func (otm *OrderTableModule) GetTotalRevenue() (float64, error) {
    fmt.Println("Calculating total revenue")
    var total float64
    err := otm.db.QueryRow(fmt.Sprintf("SELECT SUM(total) FROM %s WHERE status = 'completed'", otm.tableName)).Scan(&total)
    if err != nil {
        return 0, err
    }
    return total, nil
}

// Usage
func main() {
    db, err := sql.Open("mysql", "user:password@/dbname")
    if err != nil {
        panic(err)
    }
    defer db.Close()
    
    customerModule := NewCustomerTableModule(db)
    orderModule := NewOrderTableModule(db)
    
    customers, _ := customerModule.GetAllCustomers()
    customer, _ := customerModule.GetCustomerById(1)
    totalRevenue, _ := orderModule.GetTotalRevenue()
    
    fmt.Printf("Total customers: %d\n", len(customers))
    fmt.Printf("Total revenue: $%.2f\n", totalRevenue)
}

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

Источник