语法经验
约 6901 字大约 23 分钟
2025-12-04
本篇的语法经验并非基础语法介绍,而是笔者个人对部分语法的使用经验以及使用时遇到的坑,希望能够在开发上帮助他人不踩相同的坑。愿世上再无Error
如果您对基础语法不太了解或根本没有概念的话,可以先阅读
基础介绍进行初步了解。在零基础的情况下 菜鸟教程 、 C语言中文网 都是最好的引路人,基础不免有些枯燥,但是在看懂程序的那一刻相信都是有成就感的。
1.1 属性和字段
属性 和 字段 作为两个比较相似的概念,在笔者初学时一直有所混淆。不会有人用半天都搞不清吧?原来是我啊哈哈
首先,在 属性 和 字段 之前,要先知道变量是什么。变量 是一个特定的存储单元,它拥有着自定义的变量名称,可设置的变量值。
public string test = "test string"如上便是一个变量,public 是变量的适用范围的声明,string 是变量类型,test 是变量名,"test string" 是变量值。
而这个变量也可以被称作字段,是一个 公共字段,那字段和属性的区别是什么呢?按照笔者的理解简单来讲,属性 是一个可以在调用时,进行具体数据处理的变量,字段 是一个只可以改变值的变量。
举个例子,下面是同一个含义的 字段 和 属性 ,都代表的是一个存放电话号码的方式。
字段:
public string phone = "+86 123456789000"属性:
private string? _phone;
public string? phone
{
// 可以省略,让属性作为只写
get
{
if (_phone == null)
return null;
return _phone + "+86";
}
// 可以省略,让属性作为只读
set
{
_phone = value;
}
}这么一看 属性 要多写很多代码,但实际上在使用时,可以通过传入的字符串自动给电话号码添加前缀。而 get 中的处理完全可以根据需求进行编写,使用上来说,相比单一的 字段 赋值,属性 可以有更多的数据处理,可以让变量的处理更加统一。同样的 set 也可以自行数据处理。
一般来说 属性 不会单独出现,而是存在于实体类中,如下的用户实体类
public class UserInfo
{
public string username {get; set;}
private string? _phone;
public string? phone
{
get
{
if (_phone == null)
return null;
return _phone + "+86";
}
set
{
_phone = value;
}
}
public string nickname {get; set;}
}通过 username、phone、nickname 描述了 UserInfo 的属性,在使用时可以对每个 属性 赋值进行使用。区别于 字段 的赋值,实体类中的 属性 可以进行单独修改更加多样化。
就笔者个人使用体验而言,对于有着固定结构的数据,属性 可以非常简便的实现数据结构的复用。而 字段 一般会用于比较单一的变量,虽然字典类型也可以处理复杂的变量,但笔者个人认为带有 属性 的实体类可以更好的统一管理和扩展。
1.2 反射
反射 (Reflection) ,官方解释是命名空间中的 System.Reflection 类以及 System.Type 可用于获取有关加载的程序集及其中定义的类型的信息,例如类、接口和值类型(即结构和枚举)。 还可以使用 反射 在运行时创建类型实例,以及调用和访问它们。
笔者个人经验而言,可以简单的理解为 反射 可以认为是在程序中调用外部文件以及文件内的程序。非常典型的来说,现有dll文件,如果需要使用内部的程序、方法或函数,那便可以使用反射来调用(虽然还是不怎么用)。
是否会觉得用 反射 调用dll的方法有些多此一举,明明可以直接用添加项目引用功能直接添加dll文件,编写反射反而还变得麻烦了。其实 反射 的作用是为了动态调用,只需要将 反射 进行初步封装,就可以根据传入的参数调用指定的dll文件和方法。
类库DLL:
// dll内程序
namespace TestClassLibrary
{
public class TestClass
{
public static void test(string input)
{
Console.WriteLine(input);
}
}
}项目调用:
using System.Reflection;
namespace TestApp
{
internal class Program
{
static void Main(string[] args)
{
string DllPath = @"your_dll_path";
// 加载dll文件
Assembly asm = Assembly.LoadFrom(DllPath);
// 获取类名,必须使用 命名空间+类名
Type t = asm.GetType("TestClassLibrary.TestClass");
// 实例化类
object o = Activator.CreateInstance(t);
// 获取指定方法
MethodInfo method = t.GetMethod("test");
// 入参参数
object[] obj =
{
"Hello world!"
};
// 对方法进行调用
var keyData = method.Invoke(o, obj);
}
}
}上述是一个简易的反射调用例子,在运行 TestApp 项目后,就会弹出控制台打印出 "Hello world!",这样就可以简易地调用其他的dll文件,稍作封装就可以实现动态调用dll库了。虽然动态调用非常便利,但由于性能问题,笔者在此不建议频繁使用拖垮整个程序的运行效率。
1.3 委托和事件
1.3.1 委托
委托 (Delegate),是 C# 中比较特殊的语法,在各种地方都很常用。笼统点讲,委托 是一种类型安全的函数指针,它允许将方法作为参数传递给其他方法。如果需要详细了解可以查阅 微软官方委托简介。
委托 是一个统称,具体上来说分为一下四类 可以在自己的环境中调试运行熟悉一下:
- delegate 普通委托,可以无返回值也可以指定返回值类型
// 定义委托
public delegate int TestDelegate(int x, int y);
// 定义被调用方法
public static int Add(int x, int y)
{
return x + y;
}
static void Main(string[] args)
{
// 实例化委托
TestDelegate test_delegate = new TestDelegate(Add);
// 传入的入参调用Add方法后返回
Console.WriteLine(test_delegate(4, 6));
}- action 无返回的泛型委托
// 定义被调用方法
public static void OutputString(string input)
{
input = "input: "+ input;
Console.WriteLine(input);
}
// 将方法作为参数传递
public static void TestAction<T>(Action<T> action, T obj)
{
action(obj);
}
static void Main(string[] args)
{
// 设定泛型为string
TestAction<string>(OutputString, "Hello World");
}- func 必须有返回值的泛型委托
// 定义被调用方法
private static int Add(int a, int b)
{
return a + b;
}
// 将方法作为参数
public static int TestFunc<T1, T2>(Func<T1, T2, int> func, T1 a, T2 b)
{
return func(a, b);
}
static void Main(string[] args)
{
int result = TestFunc<int, int>(Add, 4, 6);
Console.WriteLine("result=" + result);
}- predicate 只接受一个入参且返回bool的委托
// 定义被调用的方法
private static bool CompareXY(Point obj)
{
if (obj.X > obj.Y)
return true;
else
return false;
}
static void Main(string[] args)
{
Point[] points = {
new Point(1,2),
new Point(2,1),
new Point(1,1),
new Point(2,2)
};
// 将CompareXY作为参数传入
// Array.Find()的第二个参数为predicate
Point first = Array.Find(points, CompareXY);
}简单来说,上面四个都是将 方法 作为 入参 传入委托进行调用,不同的是对 传入方法 的要求。可以通过传入相同结构的不同方法,实现更加复用性的代码,举个例子,如果需要对两个数字进行运算操作,只需要定义好对应的被调用方法,在需要的地方进行委托调用即可动态实现运算。而独立出来的被调用方法就可以统一进行扩展管理。
1.3.2 事件
事件(Event),笼统来说是用于将特定的事件通知发送给订阅者。事件通常用于实现观察者模式,它允许一个对象将状态的变化通知其他对象,而不需要知道这些对象的细节。
对笔者而言,最为熟悉的 事件 就是在 winform 项目中的,winform 就是windows窗体程序(不清楚的可以自行查阅一下,理解成正常使用的桌面程序即可)。在 winform 中时常会用到鼠标点下时触发事件、键盘按下时触发事件等。除去 winform 项目,事件也可用于其他项目。如果需要详细了解可以查阅 微软官方事件简介。
此处代码引用 菜鸟教程 的实例代码:
// 定义一个委托类型,用于事件处理程序
public delegate void NotifyEventHandler(object sender, EventArgs e);
// 发布者类
public class ProcessBusinessLogic
{
// 声明事件
public event NotifyEventHandler ProcessCompleted;
// 触发事件的方法
protected virtual void OnProcessCompleted(EventArgs e)
{
ProcessCompleted?.Invoke(this, e);
}
// 模拟业务逻辑过程并触发事件
public void StartProcess()
{
Console.WriteLine("Process Started!");
// 这里可以加入实际的业务逻辑
// 业务逻辑完成,触发事件
OnProcessCompleted(EventArgs.Empty);
}
}
// 订阅者类
public class EventSubscriber
{
public void Subscribe(ProcessBusinessLogic process)
{
// 注册实际调用的事件方法
process.ProcessCompleted += Process_ProcessCompleted;
}
// 被调用方法
private void Process_ProcessCompleted(object sender, EventArgs e)
{
Console.WriteLine("Process Completed!");
}
}
internal class Program
{
static void Main(string[] args)
{
ProcessBusinessLogic process = new ProcessBusinessLogic();
EventSubscriber subscriber = new EventSubscriber();
// 订阅事件
subscriber.Subscribe(process);
// 启动过程
process.StartProcess();
Console.ReadLine();
}
}上述示例简单实现了一个简单的发布和订阅,上述的注释已经比较完善可以先自行理解一下。发布者类 中的声明事件,就是通过实例化一个委托用于接受实际需要调用的方法。在 订阅者类 中通过给委托注册实际事件,在 OnProcessCompleted() 时就会自动触发。
还是用 winform 项目的逻辑进行解释,在属性中设置的鼠标点下时触发事件就像是提前定义的 委托,再给该 事件 添加方法时,就相当于是注册了对应的调用方法,在实际触发鼠标点下的事件时就会自动调用指定方法。
1.3.3 委托和事件的区别
这个问题几乎是八股文里的常客,在刚刚了解了 委托 和 事件 后,是否对这两个语法有了一定的了解。在笔者的经验里 事件 是 委托 的封装应用,通过对 委托 的封装提高了安全性,同时提供了发布-订阅的模式。使用上也能感受到,每次注册事件其实就是对委托进行方法的传入,和使用普通委托区别并不大。
1.4 泛型和Object
1.4.1 泛型
泛型 (Generic),是各种语言中比较常见的一个语法,在 C# 中时常会遇到 参数类型是 T 的情况。而 T 就是一般用于代指 泛型 的字母(其实也可以自定义,但后续为方便,都以 T 作为代指),此处列出标记符仅供参考。
泛型标记符:
- E Element集合元素
- T Type 实体类
- K Key 键
- V Value 值
- N Number 数值类型
- ? 表示不确定的C#类型
其实 A-Z都可以作为 泛型 标记符,上面只是一种约定,增强代码的可读性,方便团队间的合作开发。
在 微软泛型类型概述 中也对 泛型 有所描述, 泛型 是具有占位符(类型参数)的类、结构、接口和方法,用于存储或使用的一个或多个类型。 泛型 集合类可能使用类型参数作为它存储的对象类型的占位符。类型参数显示为其字段的类型及其方法的参数类型。 泛型 方法可能将其类型参数用作其返回值的类型或其正式参数之一的类型。
笔者个人的理解比较通俗,泛型 可以看作是一个容器,在实例化或定义时,就相当于是给容器倒水、饮料等各种各样的液体。在确定了容器内的类型后,返回也将会是该类型。不会出现刘谦变魔术呢样的! 在之前 委托 的示例代码中,也有使用到 泛型 ,目的也是为了让代码更为复用和便利。
public void SwapVariable<T>(ref T input_a, ref T input_b)
{
T temp = input_a;
input_a = input_b;
input_b = temp;
}上述代码就是一个简便的 泛型 例子,通过传入参数,将两个参数进行交换。而 T 便是传入的类型。如果能理解的话,可以看看之前 委托 的示例代码,代码中该代码中的 TestAction<T> 就在使用时被定义为 string 类型。
1.4.2 泛型和Object
说到这个题目时,您会不会好奇,既然已经有了 object 这个便利的万能类型,那为什么还需要有 泛型 的存在呢?总的来说,是因为 安全性。
object 作为万能类型,是所有类型的父类,可以强制转换成任意类型。
object o1 = 123;
int x = (int)o1;
object o2 = "hello";
string y = (string)o2;从上面的代码,可以很清晰的看到在赋值给对应 变量 时,需要进行强制转换而这个过程就是简易的 装箱 和 拆箱 。int 类型的123先被装箱成为 object 类型的o1,在赋值时 object 被 拆箱 赋值为y。这个过程并不安全,而且每次都需要手动强制转换也非常麻烦。
而相较于 object 的手动强制转换,泛型 并不需要 装箱拆箱 操作,在明确类型的情况下,可以让代码更加复用更加安全。所以总的来说,泛型 非常适合用于已确定类型时提高代码复用性,而 object 适合用于不确定类型,如反射等情况下,手动强制转换来确定变量类型的情况。
1.4.3 补充
上述的两小节对 泛型 和 object 都描述,除去这两个之外,还有另外两个比较常用的语法,分别是 var 和 dynamic。 此处引用几篇文章用于分辨, dynamic的正确用法 , dynamic、var、object和泛型。笔者个人习惯的话,一般在确定需要使用的类型情况下,就直接使用确定的类型,不去使用 var 、 dynamic 、 object 。在不确定的情况下,方法的参数类型会使用 泛型 让方法变得更加复用。 而 dynamic 则是在需要动态分析变量类型的情况下才会用到。至于 object 则是除非不得不用,不然一般用不到 object 来进行拆箱装箱操作。
1.5 Linq
1.5.1 语言集成查询
语言集成查询(Linq),是一种统一查询的语法,Linq 集成在C#中,可以使用相同的基本编码模式来查询和转换 XML 文档、SQL 数据库、.NET 集合和其他任何格式中的数据。很大程度上给查询的语法提供了遍历。
比如一个很简单的例子,现有一个 List 存放数据,需要从中判断对应字符串长度大于3的元素数量,就可以用 Linq 快速实现。
List<string> elementList = new List<string>()
{
"aaaaa",
"bb",
"cccc",
"ddd"
};
int target_count = elementList.Where(t => t.Length > 3).Count();target_count 的值就是通过 Linq 语句查询出的元素数量,而 Where 以及没有使用到的 Select 、 OrderBy 、 Sort 等,都是 Linq 语句。就笔者个人而言,Linq 的使用方式其实和 SQL 语句差不多太多,都可以通过自定义的逻辑进行查询,并且还可以通过对应的语句进行便捷查询。
select count(*) from elementList where length > 3对照上面的 Linq 语句和 SQL 语句,是否能感受到使用上的相似。 说明学好linq就会sql了!(划掉)
1.5.2 Linq的拆解
刚刚有介绍 Linq 的基础用法,这里将对每个部分进行拆解解释。
// 用户实体类
public class User
{
public string name {get; set;}
public int age {get; set;}
public string sex {get; set;}
public string phone {get; set;}
}
// 样本数据
List<User> userList = new List<User>()
{
new User { name = "Test1", age = 18, sex = "male", phone = "123" },
new User { name = "Test2", age = 22, sex = "female", phone = "234" },
new User { name = "Test3", age = 20, sex = "male", phone = "456" },
new User { name = "Test4", age = 25, sex = "female", phone = "567" },
new User { name = "Test5", age = 19, sex = "male", phone = "678" },
};
List<User> target_user = userList
.Where(t => t.sex.ToLower() == "male")
.OrderBy(t => t.age)
.ToList();List<User>是User的列表,也可以说是样本数据源。userList.Where()就是对数据源进行条件筛选操作,同理OrderBy和ToList也是对数据源进行的操作。(t => code)括号内的t代表循环项类似foreach循环。t.sex.ToLower()是对循环项的属性进行处理,可以通过属性和属性的内置方法对属性进行预先处理。t.sex.ToLower() == "male"代表的是条件判断,和正常的if语句一样,也可以使用逻辑运算符进行多条件判断。
上述对具体的 Linq 语句进行拆解,总的来说,Linq 提供了一种统一的语句对指定类型的数据源进行查询和筛选。在 C# 中是非常好用的一种语法,常见于筛选和判断数据集内指定条件的数据。
1.6 自动化
1.6.1 UI自动化概述
UI自动化 解释起来就是字面意思,自动化进行UI的交互。在此处要进行类别的区分,UI分为 Web UI 和 窗口UI,此处进行 窗口UI 的自动化操作分享, 窗口UI 基本建立在 windows 系统中,本段后续使用的也是 windows。
在 微软官网 中对 UI自动化 的概述如此,Microsoft UI 自动化是适用于 Microsoft Windows 的新可访问性框架,可在支持 Windows Presentation Foundation(WPF)的所有操作系统上使用。UI自动化提供对桌面上大多数用户界面(UI)元素的编程访问,使屏幕阅读器等辅助技术产品能够向最终用户提供有关UI的信息,并通过非标准输入方式操控UI。 UI自动化还允许自动测试脚本与UI交互。
看着非常官方,对笔者而言最先接触到的 UI自动化 是按键精灵,自动控制键盘与鼠标进行操作。而 C# 中的 UI自动化 就可以看成是升级版,能够更加深度进行自动化操作和控制。在提到 C# 的 UI自动化 ,就不得不说到几样东西了,UIAutomation 、 inspect.exe 、 win32 API。下面将会对此简述。
UIAutomation是C#中的一个非常强大的自动化库,提供了API可以快速进行窗口UI的自动化。inspect.exe该工具是一个图形用户界面 (GUI) 应用程序,可用于收集用于提供程序和客户端开发和调试的 UI 自动化信息,它包含在 Windows SDK 中。win32 API(应用程序编程接口)是用于开发 Windows 操作系统应用程序的核心接口,其中包含很多底层的系统操作。
1.6.2 UIAutomation
UIAutomation 是 .net framework 提供的一个自动化框架,用于访问、控制、测试 windows 桌面应用程序的UI元素。它遵循 Microsoft UI Automation (UIA) 规范,是 windows 官方的UI自动化技术。
笔者自己在使用这个库的情况,一般是用于工作中的一些自动化测试和自己兴趣上的一些自动化脚本,一下举一个比较简单的例子。
using System.Windows.Automation;
// 获取根节点
var root = AutomationElement.RootElement;
// 查找计算器窗口
var condition = new PropertyCondition(AutomationElement.NameProperty, "计算器");
var calcWindow = root.FindFirst(TreeScope.Children, condition);
// 查找按钮(例如“7”)
var buttonCond = new PropertyCondition(AutomationElement.NameProperty, "7");
var button_7 = calcWindow.FindFirst(TreeScope.Descendants, buttonCond);
// 点击按钮
var invoke = button_7.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
invoke.Invoke();分部解析:
var root = AutomationElement.RootElement;AutomationElement.RootElement 是根节点,可以理解为 html 中的顶级 <html> 标签,更通俗一点可以理解为整个桌面。对于代码来说,所有的窗口和控件,都是在 RootElement 下的子级。
var condition = new PropertyCondition(AutomationElement.NameProperty, "计算器");创建搜索条件,搜索 AutomationElement.NameProperty 为 计算器 的元素, AutomationElement.NameProperty 一般是指窗口或元素的标题。
var calcWindow = root.FindFirst(TreeScope.Children, condition);搜索到 计算器 的窗口,从根节点开始搜索,以 TreeScope.Children 的模式进行搜索,搜索条件为 condition。 TreeScope 是UI自动化中的枚举,其中包含 Chlidren 表示搜索直接子级, FindFirt() 获取第一个找到的窗口。
Ancestors指定搜索包括元素的上级(包括父级)。不支持作为条件。Children指定搜索包括元素的直接子级。Descendants指定搜索包括元素的子代(包括子级)。Element指定搜索包括元素本身。Parent指定搜索包括元素的父级。不支持作为条件。Subtree指定搜索包括搜索的根和全部子代。
var buttonCond = new PropertyCondition(AutomationElement.NameProperty, "7");
var button_7 = calcWindow.FindFirst(TreeScope.Descendants, buttonCond);该部分的搜索也同上,但是搜索到的 Element 元素是 Button ,在此处就是计算机的数字7按钮。
var invoke = button_7.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
invoke.Invoke();转换成可点击的控件模式, invoke.Invoke() 模拟点击按钮。
总体逻辑上来说,使用 UIAutomation 需要先从根节点一层层向下找到对应的窗口或控件进行操作使用。 RootElement 为所有元素的根节点,通过其找到对应窗口,然后以窗口作为根节点,进行具体元素的查找。在使用时,如果可以确定元素的路径不会变更,那也可以将路径保存后直接调用元素,省去每次都查找的时间。此处也可以通过上述有提到的 inspect.exe 进行图形化的获取。
1.6.3 Win32 API
Win32 API 是自动化中常用到的程序编程接口,上文中也有提到。可能会有疑惑为什么在已经有 UIAutomation 的情况下还需要 Win32 API ,此时需要确认需要自动化的窗口或控件支持微软架构,部分窗口或控件不支持使用 UIAutomation ,但可以识别系统层级的模拟输入或是消息发送,那就可以使用 Win32 API 进行自动化操作。详细可查看 Windows API索引
Windows UI 层级关系(由上至下):
应用程序
UI Framework (WinForms / WPF / Qt / Electron)
Win32 API(User32 / GDI32)
Windows内核
笔者个人的使用情况来说,部分游戏窗口或是类似新版QQ的窗口无法被直接捕获,但是可以通过基础的 Win32 API 直接进行消息发送后操作,在获取到对应窗口的句柄后通过模拟输入实现需要的自动化,以下是示例:
internal static class User32
{
//设置键盘按键和动作
[DllImport("user32.dll")]
internal static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
}
public static void PressDown(Key key)
{
User32.keybd_event((byte)key, 0, 0u, UIntPtr.Zero);
}
public static void PressUp(Key key)
{
User32.keybd_event((byte)key, 0, 2u, UIntPtr.Zero);
}
public static void Press(Key key)
{
PressDown(key);
PressUp(key);
}上述代码通过引用 user32.dll 使用键盘的点击事件,通过封装后将其分别实现为:按下、抬起和按键,在需要使用的地方进行函数调用即可使用 Win32 API 实现键盘操作。
1.6.4 inspect.exe使用
inspect.exe 是微软的辅助功能工具,是 Windows SDK 中的小工具。微软官方介绍 中也对 inspect.exe 有着初步介绍。
1.7 MVC模式
1.7.1 MVC模式概述
MVC模式 ,即模型-视图-控制器(Model-View-Controller)模式,是一种广泛应用于软件工程的设计模式。它将软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller),以实现应用程序的分层管理和责任分离,从而提高了代码的可维护性、可扩展性和可重用性。
上述是 MVC模式 的基础概述,但是在实际使用中,基本都会使用前后端分离的方式进行开发,按照如上的简述缺少了视图(View)部分。所以实际在 C# 的后端接口开发中,实际使用的是模型(Model)和控制器(Controller)进行开发的。
1.7.2 模型(Model)
主要功能:
处理数据和业务逻辑:模型负责封装应用程序的数据和业务逻辑。它定义了数据的结构,提供对数据的操作方法,并且负责校验数据的有效性。
与数据库交互:模型可以包含与数据库交互的代码,如数据访问对象(DAO)或对象关系映射(ORM)。
处理数据和业务逻辑部分可以理解为,给对应需要的项目创建实体类来使用,此处主要讲述与数据库交互部分,主要分为两种形式: 代码驱动数据库 和 数据库驱动代码 ,后续举例使用到的ORM Nuget库为 EntityFramework。
代码驱动数据库 ,如字面意思所言,通过先创建实体类后再有对应的ORM自动生成对应的数据库表结构,此类方式适合于:新创建项目、数据库可由开发团队控制、敏捷开发频繁修改表结构、微服务项目。以下为实际代码举例:
namespace SQLManager.Models
{
[Table("table_name")]
public class TestModel
{
[Key]
[Column("id")]
public int Id{get;set;}
[Column("name")]
public string Name{get;set;}
[Column("status")]
public int Status{get;set;}//0:actived 1:deleted
[Column("createtime")]
public DateTime Createtime{get;set;}
[Column("updatetime")]
public DateTime Updatetime{get;set;}
}
}
namespace SQLManager
{
public class RecordDbContext : DbContext
{
private IConfiguration configuration;
private string target_config;
public RecordDbContext(IConfiguration input_config, string input_db)
{
configuration = input_config;
target_config = input_db;
}
public DbSet<TestModel> Tests { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connStr = configuration.GetConnectionString(target_config);
optionsBuilder.UseMySql(connStr, ServerVersion.AutoDetect(connStr), null);
}
}
}数据库驱动代码 ,同样如字面意思所言,通过已有的数据库表结构和ORM映射生成对应的实体类,此类方式适合于:数据库统一控制无法修改或操作、表结构复杂,具体使用方式可根据实际使用的ORM框架及工具进行确定。
笔者个人而言,使用代码驱动数据库较多,在个人项目中通过该形式进行数据库表结构的建立可以避免手动建立时,实体类和数据库表结构的参数不一致。
1.7.3 视图(View)
主要功能:
展示数据:视图负责将模型的数据以用户友好的方式展示给用户。它通常是一个用户界面元素,如HTML页面、JSP页面或页面片段。
不包含业务逻辑:视图不应该包含任何业务逻辑,其唯一的职责就是展示数据。
标准的 MVC模式 下,模型、视图、控制器三层应该独立存在于同一个项目内,但就如概述内提到的情况,实际使用时大多为前后端分离的开发模式,而视图(View)则完全由前端负责,除去提到的HTML、JSP等,笔者个人最常使用的语音应该是React,通过 MVVM模式 进行前端开发和视图构建。至于 MVVM模式 可以粗略理解为前端获取到数据后,统一归整进视图模型(ViewModel)就相当于前端适配页面的数据结构,然后在具体的视图(View)绑定显示。
1.7.4 控制器(Controller)
主要功能:
处理用户请求:控制器负责接收用户的请求,并根据请求的类型和内容来处理和转发请求。
协调模型和视图:控制器通常与模型和视图进行交互,将数据从模型传递给视图进行展示,或者根据用户的操作结果选择合适的下一步操作。
简单来说,控制器(Controller)就是接口层,可以在此处定义接口供前端调用请求。
