Asp.Net MVC<一> : 三层架构、MVC

  1. 三层架构
    1. 规则
    2. 优势
    3. 劣势
  2. MVC
    1. Model
    2. View
    3. Controller
    1. MVC使用的误区
      1. 把Model理解成实体类(Entity)
      2. 大量业务逻辑代码堆积在Controller端
    2. 总结
  3. MVC变体
    1. MVP
      1. PV
      2. SC
    2. MVVM
    3. Asp.net Mvc 应用中的请求处理
  4.  

1、 三层架构

 
将整个业务应用划分为:界面层(User Interface layer, UIL)、业务逻辑层(Business Logic Layer, BLL)、数据访问层(Data access layer, DAL)。
 
 
1:界面层:主要是指用户交互的界面。用于接收用户输入的数据和显示处理后用户需要的数据。如果逻辑层相当强大和完善,无论表现层如何定义和更改,逻辑层都能完善地提供服务。
 
2:业务逻辑层:UI层和DAL层之间的桥梁实现业务逻辑。业务逻辑具体包含:验证、计算、业务规则等等。
 
3:数据访问层:与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库。(当然这些操作都是基于UI层的。用户的需求反映给界面(UI),UI反映给BLLBLL反映给DALDAL进行数据的操作,操作后再一一返回,直到将用户所需数据反馈给用户)。
 
   

1.1、 规则

  1. 最关键的,UI层只能作为一个外壳,不能包含任何业务逻辑(BizLogic)的处理过程。只有少量(或者没有)SQL语句或者存储过程的调用,并且这些语句保证不会修改数据。
  2. 设计时应该从BLL出发,而不是UI出发。BLL层在API上应该实现所有BizLogic(以面向对象的方式)。如果把UILayer拿掉,项目还能在Interface/API的层次上提供所有功能)。
  3. 不管数据层是一个简单的SqlHelper也好,还是带有Mapping过的Classes也好,应该在一定的抽象程度上做到系统无关(DAL可以移植到其他类似环境的项目)。
  4. 不管使用COM+(Enterprise Service),还是Remoting,还是WebService之类的远程对象技术,不管部署的时候是不是真的分别部署到不同的服务器上,最起码在设计的时候要做这样的考虑,更远的,还得考虑多台服务器通过负载均衡作集群(三个模块,可以分别运行于不同的服务器)。

1.2、 优势

1,结构清晰、耦合度低,

2,可维护性高,可扩展性高;

3,利于开发任务同步进行;容易适应需求变化

1.3、 劣势

1、有时会导致级联的修改。这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码

2、增加了代码量,增加了工作量,增加了复杂度。

 

2、 MVC

 

 

如果说ORM(Object Relation Mapping)等框架是数据访问层的框架模式,解除了业务逻辑和数据之间的耦合,业务逻辑不再关心底层数据如何存储和读取。所有数据呈现给业务逻辑层的就是一个个的对象。

MVC则是表现层的框架模式。用于解除业务逻辑和视图之间的耦合。从而易于扩展,便于测试。

 

MVC诞生于1979年,体现了“关注点分离”这一设计方针,它将一个人机交互应用涉及的功能分为三部分。

2.1、 Model

Model对应应用状态和业务功能的封装,可以将它理解为同时包含数据和行为的领域模型(Domain Model)。Model接受Controller的请求并完成相应的业务处理,在应用状态改变的时候可以向View发出相应的通知。

2.2、 View

View实现可视化界面的呈现并捕捉最终用户的交互操作。View可以直接调用Model查询其状态信息,而Model也可以在自己的状态发生改变时,主动通知View。

2.3、 Controller

Contoller是M和V之间的连接器,用于控制应用程序的流程。View捕获用户交互操作后直接转发给Contoller,后者完成相应的UI逻辑。如果需要涉及业务功能的调用,Contoller会直接调用Model及修改Model状态。Contoller也可以主动控制原View或创建新的View对用户交互操作予以响应。
 

2.1、 MVC使用的误区

2.1.1、 把Model理解成实体类(Entity)

在MVC中Model应该包含2部分功能,一部分是处理业务逻辑,一部分是提供View显示的数据。

它应该是业务逻辑真正的实现层。所以Model的实际上是Business Model(业务模型)。而Controller仅仅起一个“桥梁”作用,它负责把View的请求转发给Model,再负责把Model处理结束的消息通知View。Controller的存在是为了使UI界面、UI逻辑、业务逻辑之间分离。

2.1.2、 大量业务逻辑代码堆积在Controller端

MVC中的控制器,内里封装了通讯,容易变成大而全 的高度耦合的集中器。
  1. 控制器中写业务代码
    1. UI逻辑可以写在Controller中,而业务逻辑应该在Model里,Controller只是去调用它们。
  2. 控制器变得依赖信息数据中心或数据库,对象将间接地通过控制器的action耦合在一起

    1. 可以通过引入IOC容器来解决

2.2、 总结

MVC最早的定义毕竟是79年提出的,到现在GUI编程环境,业务复杂程度都有了很大改变。当采用MVC模式设计UI应用时,一般会根据开发框架的特点来对Model,View和Contoller设置一个明确的界限,同时为它们之间的交互制定一个更加严格的规则。

3、 MVC变体

在软件发展历程中出现了一些MVC变体,它们遵循定义在MVC中的基本原则,但对三元素直接的交互制定了更为严格的规范。

3.1、 MVP

  1. MVP适用于基于事件驱动的应用框架中,如Asp.net Web Forms 和Windows Forms应用。
  2. MVP中严格禁止View和Model间的直接交互,必需通过Presenter来完成。Model的独立性得到了真正的体现,它不仅仅与可视化元素的呈现(View)无关,与UI处理逻辑也无关。使用MVP的应用是用户驱动而不是Model驱动,所以Model不需要主动通知view。
  3. MVP模式中的V代表的是一个接口,一个将UI界面提炼而抽象出来的接口。接口意味着任何实现了该接口的界面,都能够复用已有的Presenter和Model代码。是真正意义上的隔离View的细节和复杂性的模式,降低了Presenter对View的依赖。带来的好处就是定义在Presenter中的UI处理逻辑变得易于测试。
 
MVP中Presenter与Model的交互很清晰,仅体现为Presenter对Model的单向调用。而View与Presenter直接的交互方式就成了MVP的核心。按照View与Presenter的交互方式依据View本身的职责范围,Martin Folwer将MVP分为PV(Passive View)和SC(Supervising Contoller)两种模式。

3.1.1、 PV

view是难以测试的,通过让它尽可能不涉及UI处理逻辑来使它不需要测试,这就是PV模式的目的。
这意味着需要将View中的UI元素通过属性的形式暴露出来。将所有UI处理逻辑全部定义到Presenter中。定义在IView里的只有属性。

3.1.2、 SC

PV需要将View中可供操作的UI元素定义在对应的接口中,对于一些复杂的富客户端应用的View来说,接口成员的数量可能会非常多,这无疑提升了代码量和Presenter的复杂度。
SC则将一些单纯,独立的UI逻辑交由View自身来完成。当然它处理的数据是Presenter实时推送给它的。View尽可能不维护数据状态。定义在IView里的接口尽量只包含方法,且是无返回值的方法(单向传递消息)。
 
Models:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Employee
{
    public string Id { get; private set; }
    public string Name { get; private set; }
    public string Gender { get; private set; }
    public DateTime BirthDate { get; private set; }
    public string Department { get; private set; }
 
    public Employee(string id,string name,string gender,DateTime birthDate,string department)
    {
        Id          = id;
        Name        = name;
        Gender      = gender;
        BirthDate   = birthDate;
        Department  = department;
    }
}
 
public class EmployeeRespository
{
    private static IList<Employee> employees;
 
    static EmployeeRespository()
    {
        employees = new List<Employee>()
        {
            new Employee("001","张三","男",new DateTime(1981,8,24),"销售部"),
            new Employee("002","李四","男",new DateTime(1981,8,24),"人事部"),
            new Employee("003","王五","女",new DateTime(1981,8,24),"人事部")
        };
    }
 
    public IEnumerable<Employee> GetEmployees(string department = "")
    {
        if (string.IsNullOrEmpty(department))
        {
            return employees;
        }
        return employees.Where(e => e.Department == department).ToArray();
    }
}
Presenter、IView
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class EmployeePresenter
{
    public IEmployeeView View { get; private set; }
    public EmployeeRespository Respository { get; private set; }
 
    public EmployeePresenter(IEmployeeView view)
    {
        this.View = view;
        this.Respository = new EmployeeRespository();
        this.View.DepartmentSelected += OnDepartmentSelected;
    }
 
    public void Initialize()
    {
        IEnumerable<Employee> employees = this.Respository.GetEmployees();
        this.View.BindEmployees(employees);
        string[] departments
            = new string[]{
                "","销售部","采购部","人事部"
            };
        this.View.BindDepartments(departments);
    }
 
    protected void OnDepartmentSelected(object sender, DepartmentSelectedEventArgs args)
    {
        string department = args.Department;
        var employees = this.Respository.GetEmployees(department);
        this.View.BindEmployees(employees);
    }
}
 
 
public interface IEmployeeView
{
    void BindEmployees(IEnumerable<Employee> employees);
    void BindDepartments(IEnumerable<string> departments);
    event EventHandler<DepartmentSelectedEventArgs> DepartmentSelected;
}
public class DepartmentSelectedEventArgs : EventArgs
{
    public string Department { get; private set; }
    public DepartmentSelectedEventArgs(string department)
    {
        this.Department = department;
    }
}
WinForm实现的UI界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public partial class Form1 : Form, IEmployeeView
{
    private EmployeePresenter Presenter{ get; private set; }
    public Form1()
    {
        InitializeComponent();
 
        Presenter = new EmployeePresenter(this);
        Presenter.Initialize();      
    }
    public event EventHandler<DepartmentSelectedEventArgs> DepartmentSelected;
 
    public void BindDepartments(IEnumerable<string> departments)
    {
        this.comboBox1.DataSource = departments;
    }
 
    public void BindEmployees(IEnumerable<Employee> employees)
    {
        this.listBox1.DataSource = employees;
        this.listBox1.DisplayMember = "Name";
    }
 
    private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
        string department = (string)this.comboBox1.SelectedItem;
        DepartmentSelectedEventArgs eventArgs = new DepartmentSelectedEventArgs(department);
        DepartmentSelected?.Invoke(sender, eventArgs);
    }
}

 

WebForm实现的UI界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public partial class Default : Page, IEmployeeView
{
    public EmployeePresenter Presenter { get; private set; }
    public event EventHandler<DepartmentSelectedEventArgs> DepartmentSelected;
 
    public Default()
    {
        this.Presenter = new EmployeePresenter(this);
    }
 
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!this.IsPostBack)
        {
            this.Presenter.Initialize();
        }
    }
 
    protected void ButtonSearch_Click(object sender, EventArgs e)
    {
        string department = this.DropDownListDepartments.SelectedValue;
        DepartmentSelectedEventArgs eventArgs = new DepartmentSelectedEventArgs(department);
        if (null != DepartmentSelected)
        {
            DepartmentSelected(this, eventArgs);
        }
    }
 
    public void BindEmployees(IEnumerable<Employee> employees)
    {
        this.GridViewEmployees.DataSource = employees;
        this.GridViewEmployees.DataBind();
    }
 
 
    public void BindDepartments(IEnumerable<string> departments)
    {
        this.DropDownListDepartments.DataSource = departments;
        this.DropDownListDepartments.DataBind();
    }
}

在MVP里,可以根据User Story来首先设计和开发Presenter。在这个过程中,View是很简单的,能够把信息显示清楚就可以了。在后面,根据需要再随便更改View, 而对Presenter没有任何的影响了。

如果要实现的UI比较复杂,而且相关的显示逻辑还跟Model有关系,可以在View和 Presenter之间放置一个Adapter。由这个 Adapter来访问Model和View,避免两者之间的关联。而同时,因为Adapter实现了View的接口,从而可以保证与Presenter之 间接口的不变。这样就可以保证View和Presenter之间接口的简洁,又不失去UI的灵活性。

3.2、 MVVM

MVVM模式中,一个ViewModel和一个View匹配,它没有MVP中的IView接口,而是完全的和View绑定,所有View中的修改变化,都会自动更新到ViewModel中,同时ViewModel的任何变化也会自动同步到View上显示。
这种自动同步之所以能够的原因是ViewModel中的属性都实现了observable这样的接口,也就是说当使用属性的set的方法,都会同时触发属性修改的事件,使绑定的UI自动刷新。(在WPF中,这个observable接口是 INotifyPropertyChanged; 在knockoutjs中,是通过函数ko.observable() 和ko.observrableCollection()来实现的)
在MVP中,V是接口IView, 解决对于界面UI的耦合; 而MVVM则干脆直接使用ViewModel和UI无缝结合, ViewModel直接就能代表UI。但是MVVM做到这点是要依赖具体的平台和技术实现。 这也就是为什么ViewModel不需要实现接口的原因,因为对于具体平台和技术的依赖,本质上使用MVVM模式就是不能替换UI的使用平台的。
MVVM的提出源于WPF,主要是用于分离应用界面层和业务逻辑层。WPF/Siverlight是基于数据驱动开发的。
在MVC和MVP模式中, 开发View层的是程序员, 虽然UI/UE团队会做很多工作, 但这个层的"实现者"仍然是程序员。
而在WPF开发中将View层的代码逻辑抽取出来,并使View层很纯粹以便完全让美工去打造它。相应地, 需要将View层的相应逻辑抽取到一个代码层上,以便让程序员专注在这里。

3.3、 Asp.net Mvc 应用中的请求处理

  1. 每个HTTP请求的目标是Controller中的一个Action,具体体现为定义在Controller类型中的一个public方法。所以对请求的处理最终体现为对目标Controller对象的激活和对目标Action方法的执行。
  2. Controller的类型和Action方法的名称及作为Action方法的部分参数可以直接通过请求的Url解析出来。
  3. 通过一个拦截器(Interceptor)对抵达Web服务器的HTTP请求进行拦截。这个拦截器根据当前请求解析出目标Controller的类型和对应的Action方法的名称,随后目标Controller被激活,相应的Action方法被执行。
  4. Action方法执行过程中,可以调用Model获取相应的数据及改变其状态。在Action执行的最后阶段一般会创建一个View,后者最终被转换为HTML以HTTP响应的形式返回到客户端。
  5. 绑定在View上的数据称为ViewModel,来源于Model或者基于显示要求进行的简单逻辑计算。它与MVVM中的ViewModel是完全不同的概念,后者不仅包括呈现在View中的数据,也包括数据操作行为。

 


 

posted @   随心~  阅读(11152)  评论(1编辑  收藏  举报
编辑推荐:
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
阅读排行:
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!

点击右上角即可分享
微信分享提示