在开发MVC应用程序时,我们应该考虑设计原则。
介绍
我们可以按照两种不同的方法在ASP.NET中构建Web应用程序
- WebForms
- MVC
WebForms
Webforms是开发Web应用程序的传统方式。它允许我们使用与传统桌面应用程序相同的事件处理模型开发Web应用程序。这提供了一些优点,如:
快速应用开发 WebForms使我们能够快速地开发Web应用程序。它提供服务器控件和其他实用程序,我们可以在相对较短的时间内开发Web应用程序。
HTTP Webforms的抽象提供了HTTP协议的抽象,这使得我们开发Web应用程序的经验 就像Windows应用程序开发一样。
虽然以上是明显的优势,但传统方法中存在不少问题
- 由于不同应用组件之间的紧密耦合,难以维护和改变。
- 单元测试是困难的,因为不同组件之间的依赖。
- 不允许我们直接使用HTTP协议,因为抽象,如服务器控件和视图状态。
MVC
MVC为上述问题提供了解决方案
分离问题 在MVC应用程序中包含具有不同职责的单独组件。由于这种分离问题,我们可以非常容易地更改一个 组件,而不影响应用程序的其余部分,因为组件之间存在松耦合。
单元测试 由于请求直接调用控制器中的操作方法,所以我们可以轻松测试控制器中的功能,而无需调用请求管道。这与ASP.NET Web窗体不同,我们必须运行请求管道来测试单个Web窗体。
但是当我们开始专门开发应用程序的大型企业应用程序时,我们可能会使用反模式,这可能会抵消MVC架构的优势。以下是一些常见的例子。
如果我们在控制器中使用某些功能,并希望在整个控制器中重用该功能,那么我们想要采取的一种方法就是将代码复制到不同的控制器上。如果我们通过复制代码重用跨控制器和模型的功能,那么我们重复代码。这是一个被称为剪切和粘贴编程的反模式的例子。
如果我们需要数据库中的一些值,并且这些值不是由现有模型类提供的,那么我们可以直接将数据访问代码放在控制器中。以下是控制器中使用EF访问数据库并更新模型对象的方法。
上面代码存在的问题:
- 对数据访问技术,实体框架有很强的依赖。
- 控制器直接访问数据库,这是模型对象的责任 。
所以我们现在明白,使用MVC框架确实需要使用SOLID原则来创建 灵活且易于维护的应用程序。这就是为什么我们要使用SOLID原则。
S.O.L.I.D面向对象设计的标准
SOLID是一套有助于面向对象设计的原则,避免了上述问题。SOLID的原则是:
单一责任原则(SRP)
第一个原则,在单一责任原则中指出,应用中的每个模块只能由于一个原因而改变。
如果我们在逻辑上看,我们可以很容易地理解它。我们在应用程序中有不同的模块,所以如果我们在单个模块中放置多个责任,这是不容易维护的,可以轻松地打破其余的应用程序。由于单个模块中有多个职责,所以一个问题会影响 其他模块。
要理解这种模式的需要,我们可以举个例子来说明这个action方法。
上述方法执行两项责任
- 返回视图
- 访问数据库。
假设如果明天我们对数据访问技术进行了一些改变,并且使用ADO.NET访问数据库。现在如果我们的代码有一些问题,错误发生,那么它将传播到我们的视图,这将通知用户我们的应用程序中的问题。这绝对不是我们想要发生的事情。
解决方案是将责任分为不同模块的单一责任模式。
正如你在上面的代码中看到的那样,我们将数据访问责任分成一个存储库类。现在我们的action方法唯一的责任就是返回视图(这是它应该做的)。
大多数应用程序中的常见情况是验证用户,并且只有在验证后才允许用户访问应用程序。通常的方法是在Login操作方法本身中具有验证逻辑。但是如果使用验证逻辑在我们的应用程序中的其他地方。例如,如果用户从某个其他站点重定向到我们的应用程序,并且我们正在不同的控制器中验证用户。
处理这种情况的更优雅的方法是实现MVC提供的IAuthenticationFilter接口。如果验证逻辑更改,我们现在只需要更改一个类,即实现IAuthenticationFilter接口的类,而不是在整个过程中查找和更改验证逻辑应用。
所以在这个改变之后,我们的方法只是一个单独的重定向语句。
开放原则(OCP)
这个原则指出,软件实体(类,模块,功能等)的扩展应该是开放的,但对修改关闭。该规则的“封闭”部分规定,一旦模块被开发和测试,代码只能被更改以纠正错误。 “开放”部分说,您应该能够扩展现有代码以引入新功能。
不促进现有类中代码更改的原因是它可能会破坏现有代码,并且如果我们更改类,还需要再次测试代码。如果我们有一些客户端代码使用该类,那么该客户端也将需要测试。
所以如果我们需要添加一些功能到现有的一个类中,那么我们可以使用继承.下面代码是一个例子,包含业务规则来计算书籍的价格。
上述模型类书包含根据买家所属类别计算书价格的逻辑,并根据其提供折扣。现在如果为折扣添加新类别,我们可以实现的唯一方法是通过改变Book类。
开放原则(OCP)说,此类应该对修改关闭,但是要扩展它,请看我们如何使用这个原则实现折扣功能。
在下面的代码中,我们创建了一个Book抽象类,含有一个CalculatePrice,抽象方法。两个类StudentBook和CorporateBook集成这个抽象类。
现在,我们可以在需要时轻松添加折扣规则,而不影响现有的代码.
里氏替换原则
这个原则说, 派生类型必须是可替代的基类型。
根据这个原则,我们可以用客户端代码中的派生类对象来替换基类对象,应用程序应该按预期工作。
如果我们认为子类和基类具有“ 继承”关系,这很容易理解。子类也是基类的一个实例。所以子类对象也应该能代替基类。
我们可以通过一个例子了解这一点。
我们有 Contractor 和 Employee 类,Contractor继承自Employee 。
起初它可能看起来是一个合理的设计。但这里有一个问题。虽然承包商在组织中工作,但他们没有员工身份。所以如果我们的客户端代码调用GetEmployeeId()方法,他可能会感到惊讶,因为该方法没有被客户端实现。
所以这里这个父子类的关系不是一个正确的关系,而且都应该是单独的类。
接口隔离原理(ISP)
这个原则指出,客户端不应该被强制依赖于他们不使用的接口。这意味着接口中的成员数可以被依赖类看到。
换句话说,我们可以说“暴露给客户端的接口应该只包含客户端需要的方法”。
根据这个原理,不是所有的方法都使用一个接口,我们应该使用少量方法创建多个接口。这样我们的客户端没必要实现不需要的方法。
在下面的例子中,我们有一个接口IOrganization,它包含Organization不同部门的三种方法。如果任何类想要实现该接口,它将必须实现所有这三种方法。就像我们所说“ 如果任何类想要实现我们的接口,它实现所有的功能 “。
如果我们可以将接口功能分解成三个独立的接口,那不是很好吗?
在下面的例子中,我们有一个模型类来实现IEmployee接口。接口Employee 包含以下方法
- Manage
- Salary
- Department
我们还有两个实现IEmployee接口的 类
- Manager
- Executive
所以我们的新设计不会强制每个员工实现 Manage 方法。同时,经理人员可以实现IManager的具体接口。
依赖倒置原则(DIP)
依赖性反转原则被定义为 “ 高级模块不应该依赖于低级模块,两者都应该取决于抽象”。
让我们用一个非常基本的例子了解这一点。有Manager和Organization。 Organization类依赖于manager类,因为它引用了Manager类的具体实例。
您可以看到Organization类有一个Add()方法。该方法允许我们将Manager添加到Organization中的管理器列表中。上面的代码有一个问题。
组织类与Manager类紧密耦合。因此,更改Manager类也将迫使我们更改Organization类。这违反了依赖倒置原则(DIP)。
下面是同一类的其他版本,但现在我们已经移除了Organization.创建了一个接口IEmployee。所以即使Manager类改变了我们的组织将不受影响 像现在的依赖关系已被删除。
使用我们的控制器的初始示例,我们正在使用存储库来访问数据库,到目前为止,控制器取决于具体的实例类型为UserRepository.这个不符合依赖倒置原则。
在下面的示例中,我们创建了一个IRepository接口,并在构造函数中传递了一个类型为IRepository的实例。这消除了我们的控制器的依赖性,即使UserRepository发生变化,我们的控制器也不需要任何更改。只要实现了UserRepository 接口,我们甚至可以将UserRepository替换为任何其他对象。
这是用于实现依赖倒置原则的依赖注入的一个例子。
设计MVC应用程序
如果我们考虑一些最佳实践,我们可以创建良好的应用程序设计。这些是开发MVC应用程序时需要考虑的一些事项。
- 只使用普通成员使用细粒度的接口。
- 尽可能使用抽象类封装通用功能。
- 设计轻量级的类,只有单一的责任,并且当新的责任被识别创建一个新的类。
- 最小化对外部实体的依赖,使用依赖注入 ,构造函数注入是大多数情况下的最佳选择。如果我们创建一个解耦结构,那么更改一个实体或类将会破坏整个应用程序的风险较小。
- 在我们的应用程序中有许多可用于实现依赖注入的IOC容器容器。常见的例子有Unity,Castle Windsor,StructureMap、Autofac。