Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Object-Oriented Thinking in Rust
Rust 中的面向对象思维

What you’ll learn: How Java’s object-oriented instincts map into Rust, what Rust keeps from classic OOP, what it rejects, and how to redesign Java service and domain models without forcing Rust into a class hierarchy.
本章将学习: Java 的面向对象直觉如何迁移到 Rust,Rust 保留了经典 OOP 的哪些部分、明确舍弃了哪些部分,以及怎样重构 Java 的服务与领域模型,而不是强行把 Rust 写成 class 层级。

Difficulty: 🟡 Intermediate
难度: 🟡 中级

Java developers usually carry four strong OOP instincts:
Java 开发者通常会带着四种很强的面向对象直觉:

  • bundle data and behavior together
    把数据和行为绑在一起。
  • reuse through inheritance
    通过继承做复用。
  • hide implementation behind interfaces
    通过接口隐藏实现。
  • let frameworks create and wire object graphs
    让框架负责创建和装配对象图。

Rust认可其中一部分,但也会明确拒绝另一部分。
Rust agrees with some of that package, and clearly rejects the rest.

What Rust Keeps
Rust 保留了什么

  • encapsulation
    封装。
  • methods on user-defined types
    自定义类型上的方法。
  • interface-like abstraction through traits
    通过 trait 实现接口式抽象。
  • polymorphism through generics and trait objects
    通过泛型和 trait object 实现多态。

So the right takeaway is not “Rust has no OOP.”
因此,正确的理解绝不是“Rust 没有 OOP”。

The right takeaway is that Rust keeps the useful parts of OOP and drops the class-centric worldview.
更准确的理解是:Rust 保留了 OOP 里真正有用的部分,同时丢掉了以 class 为中心的世界观。

What Rust Rejects
Rust 拒绝什么

  • class inheritance as the main reuse mechanism
    把类继承当成主要复用机制。
  • “everything is an object” as the default worldview
    把“万物皆对象”当成默认世界观。
  • hidden ownership behind ambient references
    把所有权隐藏在到处可拿的引用后面。
  • framework-controlled object graphs as the normal structure source
    把框架控制的对象图当成结构的默认来源。

This is why Java-shaped Rust often feels awkward.
这也是为什么很多“Java 味道过重”的 Rust 代码会显得很别扭。

A Practical Translation Table
一张实用映射表

Java OOP habit
Java OOP 习惯
Better Rust direction
更合适的 Rust 方向
entity classstruct
service interfacetrait
abstract base classtrait plus helper struct or enum
trait 配合辅助 structenum
field injectionexplicit constructor wiring
显式构造装配
inheritance reusecomposition and delegation
组合与委托
nullable propertyOption<T>
exception flowResult<T, E>

Encapsulation Still Exists
封装依然存在

#![allow(unused)]
fn main() {
pub struct Counter {
    value: u64,
}

impl Counter {
    pub fn new() -> Self {
        Self { value: 0 }
    }

    pub fn increment(&mut self) {
        self.value += 1;
    }

    pub fn value(&self) -> u64 {
        self.value
    }
}
}

Data and methods still live together, but inheritance is no longer the glue.
数据和方法依然可以放在一起,只不过把它们粘起来的东西不再是继承层级。

Traits Are Interface-Like, Not Class-Like
Trait 更像接口,不像类

#![allow(unused)]
fn main() {
trait PaymentGateway {
    fn charge(&self, cents: u64) -> Result<(), String>;
}

struct StripeGateway;

impl PaymentGateway for StripeGateway {
    fn charge(&self, cents: u64) -> Result<(), String> {
        println!("charging {cents}");
        Ok(())
    }
}
}

Traits give Java developers familiar abstraction power, but Rust does not expect a base class to sit underneath everything.
trait 会提供 Java 开发者熟悉的抽象能力,但 Rust 并不期待所有东西下面都躺着一个基类。

Polymorphism Without Inheritance
没有继承也能实现多态

Static dispatch with generics
用泛型做静态分发

#![allow(unused)]
fn main() {
fn checkout<G: PaymentGateway>(gateway: &G, cents: u64) -> Result<(), String> {
    gateway.charge(cents)
}
}

Dynamic dispatch with trait objects
用 trait object 做动态分发

#![allow(unused)]
fn main() {
fn checkout_dyn(gateway: &dyn PaymentGateway, cents: u64) -> Result<(), String> {
    gateway.charge(cents)
}
}

For Java developers, the crucial shift is that dispatch choice is explicit instead of being silently bundled with class inheritance.
对 Java 开发者来说,最关键的变化在于:分发方式是显式选择的,而不是默认绑死在类继承上。

Composition Beats Inheritance
组合通常胜过继承

abstract class BaseService {
    protected final AuditClient auditClient;

    protected BaseService(AuditClient auditClient) {
        this.auditClient = auditClient;
    }
}
#![allow(unused)]
fn main() {
struct AuditClient;

impl AuditClient {
    fn send(&self, message: &str) {
        println!("audit: {message}");
    }
}

struct UserService {
    audit: AuditClient,
}

impl UserService {
    fn create_user(&self, email: &str) {
        self.audit.send(&format!("create user {email}"));
    }
}
}

Shared capability comes from shared fields and delegation, not from a parent class chain.
能力复用来自共享字段和委托,而不是来自一条父类链。

Closed Variation Often Wants enum
封闭变体通常更适合 enum

#![allow(unused)]
fn main() {
enum Notification {
    Email { address: String },
    Sms { number: String },
    Push { device_id: String },
}

fn send(notification: Notification) {
    match notification {
        Notification::Email { address } => println!("email {address}"),
        Notification::Sms { number } => println!("sms {number}"),
        Notification::Push { device_id } => println!("push {device_id}"),
    }
}
}

If the set of cases is already known, an enum is often more honest than an abstract hierarchy.
如果所有情况本来就已经知道了,那 enum 往往比抽象层级更诚实。

Service Design Without a DI Container
没有 DI 容器时如何设计服务

#![allow(unused)]
fn main() {
struct UserRepository;
struct EmailClient;

struct UserService {
    repo: UserRepository,
    email: EmailClient,
}

impl UserService {
    fn new(repo: UserRepository, email: EmailClient) -> Self {
        Self { repo, email }
    }
}
}

This looks more manual at first, but it becomes much easier to read and debug because the dependency graph is plain code.
这乍看会更“手工”,但由于依赖图就是普通代码,阅读和调试都会轻松很多。

Better Questions for Java Developers
更适合 Java 开发者的新问题

Instead of asking:
别再优先问这些:

  • what is the base class?
    基类是什么?
  • where is the DI container?
    DI 容器在哪?
  • which abstract service owns this behavior?
    哪一个抽象服务拥有这段行为?

Ask:
更应该问这些:

  • who owns this data?
    这份数据归谁拥有?
  • is this variation open or closed?
    这类变化是开放的还是封闭的?
  • does this behavior need static or dynamic dispatch?
    这段行为需要静态分发还是动态分发?
  • should this be a trait, a struct, or an enum?
    这里到底该用 trait、struct,还是 enum?

Once these questions change, Rust stops feeling restrictive and starts opening design space.
一旦提问方式变了,Rust 就不再像一门处处设限的语言,而会开始真正打开设计空间。