Value Object (Объект-значение)
Описание Value Object
Маленький объект для хранения величин таких как деньги или диапазон дат, равенство которых не основано на идентичности.
При работе с ООП, приходишь к выводу, что полезно разделять ссылочные объекты и объекты-значения. Объект-значение обычно гораздо меньше. Он как простой тип данных из тех языков, которые не являются полностью объектно-ориентированными.
Примеры реализации
// Value Object Pattern in JavaScript
class Money {
constructor(amount, currency) {
this.amount = amount;
this.currency = currency;
}
equals(other) {
return other instanceof Money &&
this.amount === other.amount &&
this.currency === other.currency;
}
add(other) {
if (this.currency !== other.currency) {
throw new Error('Cannot add different currencies');
}
return new Money(this.amount + other.amount, this.currency);
}
multiply(factor) {
return new Money(this.amount * factor, this.currency);
}
toString() {
return `${this.amount} ${this.currency}`;
}
}
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
equals(other) {
return other instanceof Point &&
this.x === other.x &&
this.y === other.y;
}
distance(other) {
const dx = this.x - other.x;
const dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
// Usage
const money1 = new Money(100, 'USD');
const money2 = new Money(50, 'USD');
const money3 = money1.add(money2);
console.log(money3.toString()); // 150 USD
const point1 = new Point(0, 0);
const point2 = new Point(3, 4);
console.log(point1.distance(point2)); // 5
// Value Object Pattern in C++
#include <iostream>
#include <string>
#include <cmath>
class Money {
private:
double amount;
std::string currency;
public:
Money(double amount, const std::string& currency)
: amount(amount), currency(currency) {}
bool equals(const Money& other) const {
return amount == other.amount && currency == other.currency;
}
Money add(const Money& other) const {
if (currency != other.currency) {
throw std::runtime_error("Cannot add different currencies");
}
return Money(amount + other.amount, currency);
}
Money multiply(double factor) const {
return Money(amount * factor, currency);
}
std::string toString() const {
return std::to_string(amount) + " " + currency;
}
};
class Point {
private:
double x, y;
public:
Point(double x, double y) : x(x), y(y) {}
bool equals(const Point& other) const {
return x == other.x && y == other.y;
}
double distance(const Point& other) const {
double dx = x - other.x;
double dy = y - other.y;
return std::sqrt(dx * dx + dy * dy);
}
std::string toString() const {
return "(" + std::to_string(x) + ", " + std::to_string(y) + ")";
}
};
// Usage
int main() {
Money money1(100, "USD");
Money money2(50, "USD");
Money money3 = money1.add(money2);
std::cout << money3.toString() << std::endl; // 150 USD
Point point1(0, 0);
Point point2(3, 4);
std::cout << point1.distance(point2) << std::endl; // 5
return 0;
}
// Value Object Pattern in Go
package main
import (
"fmt"
"math"
)
type Money struct {
Amount float64
Currency string
}
func NewMoney(amount float64, currency string) Money {
return Money{Amount: amount, Currency: currency}
}
func (m Money) Equals(other Money) bool {
return m.Amount == other.Amount && m.Currency == other.Currency
}
func (m Money) Add(other Money) (Money, error) {
if m.Currency != other.Currency {
return Money{}, fmt.Errorf("cannot add different currencies")
}
return Money{Amount: m.Amount + other.Amount, Currency: m.Currency}, nil
}
func (m Money) Multiply(factor float64) Money {
return Money{Amount: m.Amount * factor, Currency: m.Currency}
}
func (m Money) String() string {
return fmt.Sprintf("%.2f %s", m.Amount, m.Currency)
}
type Point struct {
X, Y float64
}
func NewPoint(x, y float64) Point {
return Point{X: x, Y: y}
}
func (p Point) Equals(other Point) bool {
return p.X == other.X && p.Y == other.Y
}
func (p Point) Distance(other Point) float64 {
dx := p.X - other.X
dy := p.Y - other.Y
return math.Sqrt(dx*dx + dy*dy)
}
func (p Point) String() string {
return fmt.Sprintf("(%.2f, %.2f)", p.X, p.Y)
}
// Usage
func main() {
money1 := NewMoney(100, "USD")
money2 := NewMoney(50, "USD")
money3, _ := money1.Add(money2)
fmt.Println(money3.String()) // 150.00 USD
point1 := NewPoint(0, 0)
point2 := NewPoint(3, 4)
fmt.Println(point1.Distance(point2)) // 5
}
# Value Object Pattern in Python
from dataclasses import dataclass
from typing import Union
import math
@dataclass(frozen=True)
class Money:
amount: float
currency: str
def __add__(self, other: 'Money') -> 'Money':
if self.currency != other.currency:
raise ValueError("Cannot add different currencies")
return Money(self.amount + other.amount, self.currency)
def __mul__(self, factor: float) -> 'Money':
return Money(self.amount * factor, self.currency)
def __str__(self) -> str:
return f"{self.amount} {self.currency}"
@dataclass(frozen=True)
class Point:
x: float
y: float
def distance(self, other: 'Point') -> float:
dx = self.x - other.x
dy = self.y - other.y
return math.sqrt(dx * dx + dy * dy)
def __str__(self) -> str:
return f"({self.x}, {self.y})"
# Usage
if __name__ == "__main__":
money1 = Money(100, "USD")
money2 = Money(50, "USD")
money3 = money1 + money2
print(money3) # 150 USD
point1 = Point(0, 0)
point2 = Point(3, 4)
print(point1.distance(point2)) # 5.0
<?php
// Value Object Pattern in PHP
class Money {
private $amount;
private $currency;
public function __construct($amount, $currency) {
$this->amount = $amount;
$this->currency = $currency;
}
public function equals(Money $other) {
return $this->amount === $other->amount &&
$this->currency === $other->currency;
}
public function add(Money $other) {
if ($this->currency !== $other->currency) {
throw new InvalidArgumentException("Cannot add different currencies");
}
return new Money($this->amount + $other->amount, $this->currency);
}
public function multiply($factor) {
return new Money($this->amount * $factor, $this->currency);
}
public function __toString() {
return $this->amount . " " . $this->currency;
}
}
class Point {
private $x;
private $y;
public function __construct($x, $y) {
$this->x = $x;
$this->y = $y;
}
public function equals(Point $other) {
return $this->x === $other->x && $this->y === $other->y;
}
public function distance(Point $other) {
$dx = $this->x - $other->x;
$dy = $this->y - $other->y;
return sqrt($dx * $dx + $dy * $dy);
}
public function __toString() {
return "({$this->x}, {$this->y})";
}
}
// Usage
$money1 = new Money(100, "USD");
$money2 = new Money(50, "USD");
$money3 = $money1->add($money2);
echo $money3 . "\n"; // 150 USD
$point1 = new Point(0, 0);
$point2 = new Point(3, 4);
echo $point1->distance($point2) . "\n"; // 5
?>
Использована иллюстрация с сайта Мартина Фаулера.