一套适用于桌面程序、Web 应用、甚至是嵌入式开发的架构思维方式,让你从“会写功能”进阶到“会设计架构”。
MCP 是一种常用的软件架构模式,全称是 Model-Controller-Presenter。如果你以前听说过 MVC(Model-View-Controller)或 MVVM(Model-View-ViewModel),那么 MCP 就是它们的“近亲”,也是为了解决相似问题而生的一种结构。
它的核心目标,是让代码的结构更清晰、职责更明确、更容易维护。
对于初学者或中小项目来说,如果直接在一个 .py
文件或 .js
文件里把 UI 逻辑、数据操作、业务逻辑混在一起,很容易陷入“越写越乱”的局面。
MCP 提供一种“分工合作”的写法,把整个程序拆成三部分:
这样的分工,让你即使三个月后再看代码,也知道谁负责干嘛。
这是应用程序的“脑袋”,主要负责:
比如,在一个记账应用中,Model 负责“记一笔账”、“删除一笔账”、“计算总金额”。
Controller 是“指挥官”,接收用户的动作并决定:
比如,用户点击了“保存”按钮,Controller 就调用 Model 的 save()
方法,然后通知 Presenter 更新页面。
Presenter 不处理业务、不管流程,只负责把数据“展示出来”:
如果你用的是 Tkinter、PyQt、HTML+JS,Presenter 就是那些操作“界面控件”的代码。
MVC 中的 View 和 Controller 通常互相交叉,不容易分清界限,新手容易“写着写着全都写进控制器”。
而 MCP 把 View 抽象成 Presenter,让展示逻辑更单纯。
MVVM 的数据绑定很强大,但新手往往被“响应式”、“双向绑定”搞晕。
MCP 更加直白——想展示就写展示函数。
不会因为改了界面导致数据逻辑出错。一个新手团队也能各司其职:
save_data()
、read_data()
show_result()
、update_table()
会导致控制器越来越臃肿。建议逻辑交给 Model,展示交给 Presenter。
Model 不能知道 UI 的结构,Presenter 也不该处理数据逻辑。
每一层只做自己该做的事,是 MCP 架构成功的关键。
普通人看代码更依赖注释,良好的文档可以帮助你自己或他人快速上手。
# model.py
class TodoModel:
def __init__(self): self.todos = []
def add(self, item): self.todos.append(item)
def list(self): return self.todos
# presenter.py
class TodoPresenter:
def show(self, todos):
print("当前任务:")
for i, todo in enumerate(todos):
print(f"{i+1}. {todo}")
# controller.py
from model import TodoModel
from presenter import TodoPresenter
m = TodoModel()
p = TodoPresenter()
while True:
cmd = input("请输入 add 或 list:")
if cmd.startswith("add"):
item = cmd[4:]
m.add(item)
elif cmd == "list":
p.show(m.list())
结构说明:
# model.py
def calculate(expr: str):
try:
return str(eval(expr))
except:
return "Error"
# presenter.py
from PyQt5.QtWidgets import QLabel
class Presenter:
def __init__(self, label: QLabel):
self.label = label
def update_display(self, text: str):
self.label.setText(text)
# controller.py
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QLabel
from model import calculate
from presenter import Presenter
class Calculator(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Calculator")
self.layout = QVBoxLayout()
self.display = QLabel("0")
self.layout.addWidget(self.display)
self.presenter = Presenter(self.display)
self.expr = ""
for btn_text in ["1", "2", "3", "+", "4", "5", "6", "-", "=", "C"]:
btn = QPushButton(btn_text)
btn.clicked.connect(lambda _, text=btn_text: self.on_click(text))
self.layout.addWidget(btn)
self.setLayout(self.layout)
def on_click(self, text):
if text == "=":
result = calculate(self.expr)
self.presenter.update_display(result)
self.expr = ""
elif text == "C":
self.expr = ""
self.presenter.update_display("0")
else:
self.expr += text
self.presenter.update_display(self.expr)
if __name__ == "__main__":
app = QApplication([])
window = Calculator()
window.show()
app.exec_()
# model.py
import requests
def get_weather(city):
url = f"http://wttr.in/{city}?format=3"
res = requests.get(url)
return res.text
# presenter.py
from flask import render_template_string
HTML_TEMPLATE = """
{% if weather %}
天气:{{ weather }}
{% endif %}
"""
def show_result(weather=""):
return render_template_string(HTML_TEMPLATE, weather=weather)
# controller.py
from flask import Flask, request
from model import get_weather
from presenter import show_result
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
city = request.form.get("city")
weather = get_weather(city)
return show_result(weather)
return show_result()
if __name__ == "__main__":
app.run(debug=True)
# model.py
students = []
def add_student(name, score):
students.append((name, score))
def get_all_students():
return students
# presenter.py
from tkinter import ttk
class Presenter:
def __init__(self, tree: ttk.Treeview):
self.tree = tree
def update_list(self, data):
for item in self.tree.get_children():
self.tree.delete(item)
for name, score in data:
self.tree.insert("", "end", values=(name, score))
# controller.py
import tkinter as tk
from tkinter import ttk
from model import add_student, get_all_students
from presenter import Presenter
root = tk.Tk()
root.title("成绩管理")
entry_name = tk.Entry(root)
entry_score = tk.Entry(root)
entry_name.pack()
entry_score.pack()
tree = ttk.Treeview(root, columns=("姓名", "分数"), show="headings")
tree.heading("姓名", text="姓名")
tree.heading("分数", text="分数")
tree.pack()
presenter = Presenter(tree)
def on_add():
name = entry_name.get()
score = entry_score.get()
add_student(name, score)
presenter.update_list(get_all_students())
btn = tk.Button(root, text="添加", command=on_add)
btn.pack()
root.mainloop()
// App.jsx
import React, { useState } from 'react';
const products = [
{ id: 1, name: "手表" },
{ id: 2, name: "耳机" },
];
function App() {
const [favorites, setFavorites] = useState([]);
const toggleFavorite = (id) => {
setFavorites((prev) =>
prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]
);
};
return (
<div>
{products.map((p) => (
<div key={p.id}>
<h3>{p.name}</h3>
<button onClick={() => toggleFavorite(p.id)}>
{favorites.includes(p.id) ? "已收藏" : "收藏"}
</button>
</div>
))}
</div>
);
}
export default App;
# model.py
records = []
def add_record(item, amount):
records.append((item, amount))
def get_total():
return sum(amount for _, amount in records)
def get_records():
return records
# main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from model import add_record, get_total, get_records
class Book(BoxLayout):
def __init__(self, **kwargs):
super().__init__(orientation='vertical', **kwargs)
self.item_input = TextInput(hint_text="项目")
self.amount_input = TextInput(hint_text="金额")
self.output = Label(text="当前余额:0")
btn = Button(text="添加")
btn.bind(on_press=self.on_add)
self.add_widget(self.item_input)
self.add_widget(self.amount_input)
self.add_widget(btn)
self.add_widget(self.output)
def on_add(self, _):
add_record(self.item_input.text, float(self.amount_input.text))
total = get_total()
self.output.text = f"当前余额:{total}"
class MyApp(App):
def build(self):
return Book()
if __name__ == "__main__":
MyApp().run()
MCP 架构是一种既适合新手、又能支持复杂应用的架构方式。
它的优势在于:
最后,写好架构的秘诀永远是:“代码是给人读的,顺带让机器运行”。
希望这篇指南能成为你编程路上的结构启蒙!
点个赞 / ⭐ 收藏 / 留个言支持我吧!