Layer Supertype (Супертип Уровня)

Описание Layer Supertype

Тип, выступающий родительским для всех типов в своём уровне

Нередко все классы одного уровня имеют одинаковые методы, которые не хочется дублировать повсеместно. Для того, чтобы избежать дублирования, можно все общие методы перенести в один класс (Layer Supertype), который будет являться Супертипом (читай - родителем) всех классов в своём уровне.

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

// Layer Supertype Pattern in JavaScript
class DomainObject {
    constructor(id) {
        this.id = id;
        this.createdAt = new Date();
        this.updatedAt = new Date();
    }
    
    getId() {
        return this.id;
    }
    
    getCreatedAt() {
        return this.createdAt;
    }
    
    getUpdatedAt() {
        return this.updatedAt;
    }
    
    updateTimestamp() {
        this.updatedAt = new Date();
    }
    
    validate() {
        // Base validation - can be overridden
        return this.id != null && this.id > 0;
    }
    
    save() {
        if (this.validate()) {
            console.log(`Saving ${this.constructor.name} with ID: ${this.id}`);
            this.updateTimestamp();
            return true;
        }
        return false;
    }
    
    delete() {
        console.log(`Deleting ${this.constructor.name} with ID: ${this.id}`);
        return true;
    }
}

// Domain entities that extend the supertype
class User extends DomainObject {
    constructor(id, name, email) {
        super(id);
        this.name = name;
        this.email = email;
    }
    
    validate() {
        return super.validate() && 
               this.name && 
               this.email && 
               this.email.includes('@');
    }
    
    getDisplayName() {
        return `${this.name} (${this.email})`;
    }
}

class Product extends DomainObject {
    constructor(id, name, price) {
        super(id);
        this.name = name;
        this.price = price;
    }
    
    validate() {
        return super.validate() && 
               this.name && 
               this.price > 0;
    }
    
    getFormattedPrice() {
        return `$${this.price.toFixed(2)}`;
    }
}

// Usage
const user = new User(1, 'John Doe', 'john@example.com');
const product = new Product(1, 'Laptop', 999.99);

console.log('User validation:', user.validate());
console.log('User display:', user.getDisplayName());
user.save();

console.log('Product validation:', product.validate());
console.log('Product price:', product.getFormattedPrice());
product.save();
<?php
// Layer Supertype Pattern in PHP
abstract class DomainObject {
    protected $id;
    protected $createdAt;
    protected $updatedAt;
    
    public function __construct($id) {
        $this->id = $id;
        $this->createdAt = new DateTime();
        $this->updatedAt = new DateTime();
    }
    
    public function getId() {
        return $this->id;
    }
    
    public function getCreatedAt() {
        return $this->createdAt;
    }
    
    public function getUpdatedAt() {
        return $this->updatedAt;
    }
    
    protected function updateTimestamp() {
        $this->updatedAt = new DateTime();
    }
    
    public function validate() {
        // Base validation - can be overridden
        return $this->id !== null && $this->id > 0;
    }
    
    public function save() {
        if ($this->validate()) {
            echo "Saving " . get_class($this) . " with ID: {$this->id}\n";
            $this->updateTimestamp();
            return true;
        }
        return false;
    }
    
    public function delete() {
        echo "Deleting " . get_class($this) . " with ID: {$this->id}\n";
        return true;
    }
}

// Domain entities that extend the supertype
class User extends DomainObject {
    private $name;
    private $email;
    
    public function __construct($id, $name, $email) {
        parent::__construct($id);
        $this->name = $name;
        $this->email = $email;
    }
    
    public function validate() {
        return parent::validate() && 
               !empty($this->name) && 
               !empty($this->email) && 
               strpos($this->email, '@') !== false;
    }
    
    public function getDisplayName() {
        return "{$this->name} ({$this->email})";
    }
}

class Product extends DomainObject {
    private $name;
    private $price;
    
    public function __construct($id, $name, $price) {
        parent::__construct($id);
        $this->name = $name;
        $this->price = $price;
    }
    
    public function validate() {
        return parent::validate() && 
               !empty($this->name) && 
               $this->price > 0;
    }
    
    public function getFormattedPrice() {
        return '$' . number_format($this->price, 2);
    }
}

// Usage
$user = new User(1, 'John Doe', 'john@example.com');
$product = new Product(1, 'Laptop', 999.99);

echo "User validation: " . ($user->validate() ? 'true' : 'false') . "\n";
echo "User display: " . $user->getDisplayName() . "\n";
$user->save();

echo "Product validation: " . ($product->validate() ? 'true' : 'false') . "\n";
echo "Product price: " . $product->getFormattedPrice() . "\n";
$product->save();
?>
// Layer Supertype Pattern in Go
package main

import (
    "fmt"
    "time"
)

type DomainObject struct {
    ID        int
    CreatedAt time.Time
    UpdatedAt time.Time
}

func NewDomainObject(id int) *DomainObject {
    now := time.Now()
    return &DomainObject{
        ID:        id,
        CreatedAt: now,
        UpdatedAt: now,
    }
}

func (do *DomainObject) GetID() int {
    return do.ID
}

func (do *DomainObject) GetCreatedAt() time.Time {
    return do.CreatedAt
}

func (do *DomainObject) GetUpdatedAt() time.Time {
    return do.UpdatedAt
}

func (do *DomainObject) UpdateTimestamp() {
    do.UpdatedAt = time.Now()
}

func (do *DomainObject) Validate() bool {
    // Base validation - can be overridden
    return do.ID > 0
}

func (do *DomainObject) Save() bool {
    if do.Validate() {
        fmt.Printf("Saving DomainObject with ID: %d\n", do.ID)
        do.UpdateTimestamp()
        return true
    }
    return false
}

func (do *DomainObject) Delete() bool {
    fmt.Printf("Deleting DomainObject with ID: %d\n", do.ID)
    return true
}

// Domain entities that embed the supertype
type User struct {
    *DomainObject
    Name  string
    Email string
}

func NewUser(id int, name, email string) *User {
    return &User{
        DomainObject: NewDomainObject(id),
        Name:         name,
        Email:        email,
    }
}

func (u *User) Validate() bool {
    return u.DomainObject.Validate() &&
           u.Name != "" &&
           u.Email != "" &&
           len(u.Email) > 0
}

func (u *User) GetDisplayName() string {
    return fmt.Sprintf("%s (%s)", u.Name, u.Email)
}

type Product struct {
    *DomainObject
    Name  string
    Price float64
}

func NewProduct(id int, name string, price float64) *Product {
    return &Product{
        DomainObject: NewDomainObject(id),
        Name:         name,
        Price:        price,
    }
}

func (p *Product) Validate() bool {
    return p.DomainObject.Validate() &&
           p.Name != "" &&
           p.Price > 0
}

func (p *Product) GetFormattedPrice() string {
    return fmt.Sprintf("$%.2f", p.Price)
}

// Usage
func main() {
    user := NewUser(1, "John Doe", "john@example.com")
    product := NewProduct(1, "Laptop", 999.99)

    fmt.Printf("User validation: %t\n", user.Validate())
    fmt.Printf("User display: %s\n", user.GetDisplayName())
    user.Save()

    fmt.Printf("Product validation: %t\n", product.Validate())
    fmt.Printf("Product price: %s\n", product.GetFormattedPrice())
    product.Save()
}

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

Источник