We Reinvented OOP by Making a Sandwich (Before Writing Any Code)

We Reinvented OOP by Making a Sandwich (Before Writing Any Code)

我们通过制作三明治重新发明了面向对象编程(在写任何代码之前)

A weak developer stares at an empty .cs file and starts typing syntax until the red squiggly lines go away. A software engineer designs the system before touching a compiler. Most programming tutorials rush straight into syntax: “Here is an int. Here is a for loop. Here is public static void Main. Good luck!” The result? Beginners hit tutorial hell. The second they face a blank screen, their brain locks up. They know the keywords, but they don’t know how to decompose a problem. 平庸的开发者盯着空白的 .cs 文件,开始敲击语法,直到红色的波浪线消失。而软件工程师在触碰编译器之前,就已经设计好了系统。大多数编程教程直接跳进语法:“这是一个整数。这是一个循环。这是主函数。祝你好运!”结果呢?初学者陷入了“教程地狱”。一旦面对空白屏幕,他们的大脑就会一片空白。他们认识关键字,却不知道如何拆解问题。

Inside our engineering mentorship program (BY MY SELF), we ran an experiment with an apprentice named John: Build the entire conceptual foundation of Object-Oriented Programming (OOP) without writing a single line of C# first. Here is the step-by-step breakdown of how it unfolded—and how it will sharpen your problem-solving skills. 在我们的工程指导项目(BY MY SELF)中,我们与一位名叫 John 的学徒进行了一项实验:在不写一行 C# 代码的情况下,构建面向对象编程(OOP)的完整概念基础。以下是这一过程的逐步拆解,以及它将如何磨练你的问题解决能力。

1. The Sandwich Challenge: Computers Have Zero Context

1. 三明治挑战:计算机没有任何上下文

To a human, “Make me a cup of coffee” is simple. Your brain automatically fills in the gaps: find a mug, boil water, add grounds, pour. A computer doesn’t know what “make” is. It doesn’t know what “coffee” is. It has zero implied context. We gave our apprentice his first challenge: The Challenge: You are programming an autonomous robot with zero real-world knowledge. It has: Bread, Butter, a Knife, a Plate, and Cheese. Write an algorithm that gives step-by-step instructions to assemble a cheese sandwich. 对人类来说,“给我冲杯咖啡”很简单。你的大脑会自动填补空白:找杯子、烧水、加咖啡粉、冲泡。但计算机不知道什么是“冲”,也不知道什么是“咖啡”。它没有任何隐含的上下文。我们给学徒布置了第一个挑战:你正在为一个没有任何现实世界知识的自动机器人编程。它拥有:面包、黄油、一把刀、一个盘子和奶酪。编写一个算法,提供组装奶酪三明治的逐步指令。

Here was his first attempt: 这是他的第一次尝试:

  1. Name the items: A = Bread, B = Butter, C = Knife, D = Plate, E = Cheese

  2. Place A on D

  3. Using C take B = F

  4. Then F is placed on A

  5. E is placed on F

  6. A + B + E = G

  7. G + D = result

  8. result = Sandwich

  9. 命名物品:A = 面包, B = 黄油, C = 刀, D = 盘子, E = 奶酪

  10. 将 A 放在 D 上

  11. 使用 C 获取 B = F

  12. 然后将 F 放在 A 上

  13. 将 E 放在 F 上

  14. A + B + E = G

  15. G + D = 结果

  16. 结果 = 三明治

It looks reasonable to a human. But let’s run an engineering audit on it. 对人类来说这看起来很合理。但让我们对它进行一次工程审计。

2. Debugging Human Logic

2. 调试人类逻辑

When we reviewed the algorithm like a senior engineer reviewing a Pull Request, three major bugs surfaced: 当我们像资深工程师审查 Pull Request 一样审查该算法时,发现了三个主要 Bug:

  • 🐛 Bug 1: Undefined Verbs (“Take”) Using C take B = F. What does “take” mean to a mechanical robot actuator? Does it slice the butter? Scoop it? Stab the block? At what angle? How many grams? Engineering Rule: Ambiguity in requirements creates defects in production. 🐛 Bug 1:未定义的动词(“获取”) 使用 C 获取 B = F。对于机械机器人执行器来说,“获取”意味着什么?是切黄油吗?挖一勺吗?还是刺入黄油块?什么角度?多少克?工程准则:需求中的歧义会导致生产中的缺陷。

  • 🐛 Bug 2: Commutativity Fallacy A + B + E = G (Bread + Butter + Cheese = Sandwich). In algebra, addition is commutative: A + B == B + A. Does physical assembly work that way? No! If you put butter under the bread, you get a greasy plate and a structural catastrophe. You cannot use mathematical operators to hide unstated physical actions. 🐛 Bug 2:交换律谬误 A + B + E = G(面包 + 黄油 + 奶酪 = 三明治)。在代数中,加法满足交换律:A + B == B + A。但物理组装是这样吗?不!如果你把黄油放在面包下面,你会得到一个油腻的盘子和一场结构性灾难。你不能用数学运算符来掩盖未说明的物理动作。

  • 🐛 Bug 3: Container Leak G + D = result (where D = Plate). If you eat the sandwich, do you eat the plate? The plate is an execution environment / container (like memory allocation). The sandwich is the data payload. Conflating your data with your runtime infrastructure leads to memory leaks and dirty architecture. 🐛 Bug 3:容器泄漏 G + D = 结果(D = 盘子)。如果你吃三明治,你会把盘子也吃掉吗?盘子是执行环境/容器(类似于内存分配)。三明治是数据负载。将数据与运行时基础设施混为一谈会导致内存泄漏和混乱的架构。

3. Inventing a Mini-Language: The Birth of “State”

3. 发明一种微型语言:“状态”的诞生

For attempt #2, the apprentice separated ingredients from tools and containers, defining operational verbs: 在第二次尝试中,学徒将配料与工具和容器分离开来,并定义了操作动词:

  • Entities: Bread, Butter, Cheese

  • Tools: Knife

  • Container: Plate

  • Operations:

    • SCOOP: Engage knife at an angle to collect butter.
    • SPREAD: Distribute scooped material evenly over a surface.
    • LAY: Place an entity onto a base.
  • 实体: 面包、黄油、奶酪

  • 工具:

  • 容器: 盘子

  • 操作:

    • SCOOP(挖):以一定角度使用刀收集黄油。
    • SPREAD(涂抹):将挖出的材料均匀分布在表面上。
    • LAY(放置):将实体放在底座上。

Then he wrote: SPREAD(Butter, Bread) -> Stack 1 然后他写道:SPREAD(Butter, Bread) -> Stack 1

Look closely at what happened here. When butter is spread onto bread, the bread doesn’t vanish into a completely new element. The bread simply undergoes a state transition: [ Bread (Plain) ] ── SPREAD(Butter) ──> [ Bread (Buttered) ]. This is one of the most critical ideas in computer science: State is the condition or configuration of an entity at a specific point in time. Actions don’t just “do things”—they trigger state transitions. 仔细观察这里发生了什么。当黄油涂在面包上时,面包并没有消失并变成一个全新的元素。面包只是经历了一次状态转换:[ 面包(原味) ] ── SPREAD(黄油) ──> [ 面包(涂了黄油的) ]。这是计算机科学中最关键的思想之一:状态是实体在特定时间点的条件或配置。动作不仅仅是“做某事”,它们触发了状态转换。

4. The Bank Test: Command-Query Separation

4. 银行测试:命令-查询分离

To ensure this model wasn’t just about physical objects, we tested it against pure information: a Bank Account. Given an account with Balance = $5,000 and Status = Active, we analyzed four operations: DEPOSIT($1,000), WITHDRAW($500), CHECK_BALANCE(), CLOSE_ACCOUNT(). 为了确保这个模型不仅仅适用于物理对象,我们用纯信息进行了测试:银行账户。假设一个账户余额 = $5,000,状态 = 活跃,我们分析了四个操作:DEPOSIT($1,000)(存款)、WITHDRAW($500)(取款)、CHECK_BALANCE()(查询余额)、CLOSE_ACCOUNT()(销户)。

When looking at CHECK_BALANCE(), the apprentice noticed something crucial: 在查看 CHECK_BALANCE() 时,学徒注意到了关键的一点:

  • State Before: Balance = $4,500

  • Action: CHECK_BALANCE()

  • State After: Balance = $4,500 (Unchanged!)

  • Output: $4,500

  • 操作前状态:余额 = $4,500

  • 动作:CHECK_BALANCE()

  • 操作后状态:余额 = $4,500(未改变!)

  • 输出:$4,500

He had just independently discovered Command-Query Separation (CQS): 他刚刚独立发现了“命令-查询分离”(CQS):

OPERATIONS
COMMANDS (Mutations)
- Change State
- Produce Side Effects
- e.g., WITHDRAW()
操作
命令(变更)查询(观察)
- 改变状态- 读取状态
- 产生副作用- 无副作用
- 例如:WITHDRAW()- 例如:CHECK_BALANCE()

An action doesn’t always mutate an entity. Some actions simply inspect and return data. 动作并不总是改变实体。有些动作只是检查并返回数据。

5. The 100-Cars Dilemma: Why Classes Exist

5. 100 辆车的困境:为什么类(Class)存在

Finally, we hit him with a scalability problem: “Imagine you’re building a racing simulation. A car has Speed, Fuel, and an Engine. It can Accelerate(), Brake(), and Refuel(). Now imagine you need 100 cars on the track. How do you design this without writing Accelerate() 100 separate times?” 最后,我们抛出了一个可扩展性问题:“想象你在构建一个赛车模拟器。一辆车有速度、燃料和引擎。它可以加速()、刹车()和加油()。现在想象赛道上有 100 辆车。你如何在不写 100 次 Accelerate() 的情况下设计这个系统?”

His response: “You create a single car template. The template defines what every car HAS and what every car DOES. Then you stamp out 100 individual cars from that template, each holding its own numbers.” 他的回答是:“你创建一个单一的汽车模板。模板定义了每辆车‘有什么’以及每辆车‘能做什么’。然后你从那个模板中复制出 100 辆独立的汽车,每辆车都持有自己的数据。”

Without reading a single programming textbook, he had just defined the relationship between a Class and an Object Instance. 在没有读过任何编程教科书的情况下,他刚刚定义了类(Class)和对象实例(Object Instance)之间的关系。

6. The Rosetta Stone: From Mental Model to C#

6. 罗塞塔石碑:从思维模型到 C#

Here is how our apprentice’s first-principles thinking maps directly to professional C#: 以下是我们的学徒的第一性原理思维如何直接映射到专业的 C# 代码中:

Conceptual ModelSoftware EngineeringC# Syntax
Car TemplateClasspublic class Car { ... }
Individual Stamped CarInstance / ObjectCar car1 = new Car(0, 50);
Speed, Fuel, StatusProperties / Fieldspublic double Speed { get; set; }
Accelerate, BrakeMethods (Behaviors)public void Accelerate() { ... }
概念模型软件工程C# 语法
汽车模板类 (Class)public class Car { ... }
单个复制出的汽车实例 / 对象 (Instance / Object)Car car1 = new Car(0, 50);
速度、燃料、状态属性 / 字段 (Properties / Fields)public double Speed { get; set; }
加速、刹车方法 (Methods / Behaviors)public void Accelerate() { ... }