前言
一个星期前走项目走到了三层架构,刚好老师又组织开会,询问项目进度的情况,也正好有几个同学进度一起都到了机房重构的开始阶段,于是和那几个同学一起组织起来开始了机房重构。
当时三层架构刚学的差不多,虽说也消化理解到位了,但哪知道机房重构用的是七层呀,所以在七层未彻底消化的情况下便潦潦草草的开始了机房的七层登录,导致了七层架构的理解不到位致使后面困难重重,各个层的代码总是搭错。后来也是再次参考他人的博客,深刻去探讨,去理解,才算是把“七层”消化下去。
那么,
何为“七层”呢?
“七层”是在三层架构的基础上拓展出来的,其实和三层架构的原理差不多,只是随着项目的发展需要而分离多个层出来的,我们需要明确的是层次不是越多越好,要结合项目的实际需要而分层。三层架构分为界示层(UI)、业务逻辑层(BLL)、数据访问层(DAL)。七层架构则为界面层(UI)、业务逻辑层(BLL)、数据访问层(DAL)、外观层(Facade)、实体层(Entity/Model)、数据访问接口层(IDAL)、数据访问工厂层(Factory)。
“七层”的组成与作用
- 界面层(UI):也称表示层,位于最上层,用于显示和接收用户提交的数据,为用户提供交互式的界面。表示层一般为Windows窗体应用程序或Web应用程序。收集用户输入的数据后传给外观层(Facade),再由外观层(Facade)传给业务逻辑层(BLL)进行相应的判断。
- 业务逻辑层(BLL):是界面层(UI)和数据访问层(DAL)之间沟通的桥梁,主要负责数据的传递和处理,然后再调用工厂中的方法创建相应的接口。
- 数据访问层(DAL):直接与数据库打交道,提供数据访问的方法,不存储逻辑,主要实现对数据的读取、保存和更新等操作。在接口中对数据库操作语句进行组合装配。数据访问层是数据库的管理者,但不是访问者,不直接与数据库发生关联。数据库中每个表都对应一个数据访问层的 (访问控制)类。在数据访问层中有SQLHelper类,专用于存放公用的访问数据库方法以让其他访问控制类调用,实现代码复用。数据访问层(DAL)只与接口层(IDAL)、实体类(Entity)有关联关系。
-
外观层(Facade):增加外观可以提供一个简单的接口,减少层与层之间的依赖,进而使得界面层(UI)和业务逻辑层(BLL)耦合度大大降低,界面层(UI)和业务逻辑层(BLL)之间的联系只需要通过外观层(Facade)的接口就行了,界面层(UI)无需知道业务逻辑层(BLL)内部有哪些方法。外观层(Facade)接收界面层(UI)传来的数据,然后调用业务逻辑层(BLL)的方法对信息进行验证。
- 实体层(Entity/Model):存放全局的实体类,相当于加强的数据结构,实现了对数据的封装。数据库中每个表都对应一个实体类,表的字段就是实体类的属性,类型一一对应。界示层(UI)、业务逻辑层(BLL)、数据访问层(DAL)三层的交互主要就是通过实体类作为参数,并返回信息。实体类不与任何层发生关联关系。
- 数据访问接口层(IDAL):定义一个统一的接口,解除业务逻辑层(BLL)和数据访问层(DAL)的耦合。
- 数据访问工厂层(Factory):定义一个接口调用接口层,实现BLL层和DAL层之间的数据传递;抽象工厂+反射+配置文件,作用是灵活的实现数据库的连接,通过配置文件和抽象工厂我们可以实现不更改代码,换一下配置文件中的value值就可以更换数据库,进一步解耦合。
”七层“之间的关系图
“七层”之间的关联
BLL |
Entity、Factory、IDAL |
DAL |
Entity、IDAL |
Entity |
无 |
Facade |
BLL、Entity |
Factory |
IDAL |
IDAL |
Entity |
UI |
Entity、Facade |
“七层”登录代码模块
界面层(UI)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace UI
{
public partial class frmLogin : Form
{
public frmLogin()
{
InitializeComponent();//跳转到Designer的InitializeComponent方法
} //InitializeComponent方法走完之后跳到这儿,跳回Programs
private void btnOK_Click(object sender, EventArgs e)
{
if(txtName.Text.Trim()=="")
{
MessageBox.Show("请输入你的用户名!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtName.Focus();
return;
}
if(txtPwd.Text=="")
{
MessageBox.Show("请输入密码", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtPwd.Focus();
return;
}
//实例化一个外观
Facade.LoginFacade facade = new Facade.LoginFacade();
//实例化一个用户
Entity.UserInfo user = new Entity.UserInfo();
//接收信息
user.UserID = txtName.Text; //把在文本框中输入的东西转化为int32类型赋值给UserID
user.Password = txtPwd.Text;//密码赋值给Password
//调用外观方法,返回给user
Boolean flag = false;//一个布尔值,在程序中控制是否允许移动;
//当鼠标左键被按下时,flag=True,此时允许MouseMove事件激发,同时设定允许成体位置随着鼠标移动而移动;
//当松开鼠标左键时,即使鼠标移动,窗体也不能随着其移动而移动,所以flag=false
Facade.LoginFacade flogin = new Facade.LoginFacade(); //实例化外观flogin
flag = flogin.SelectUser(user); //调用外观方法,返回给user,跳转到Facade层
//判断是否登录成功
if (flag !=false)
{
MessageBox.Show("登录成功");
this.Hide();
this.DialogResult = System.Windows.Forms.DialogResult.OK;//用于获取对话款返回的结果,比如一个提示框"确定要删除吗?"
Form a = new Form();
a.Show();
}
else
{
MessageBox.Show("用户名或密码不正确");
txtName.Text = "";
txtPwd.Text = "";
txtName.Focus();
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (MessageBox.Show("确定退出系统吗?", "提示", MessageBoxButtons.OKCancel, MessageBoxIcon.Asterisk) == DialogResult.OK)
{
System.Environment.Exit(0);//强制退出,最彻底的退出方式
}
//this.Close();//只是关闭当前窗口,若不是主窗体的话,是无法退出程序的,另外若是有托管线程(非主线程),也无法干净的退出
//Application.Exit()://强调所有消息终止,退出所有的窗体,但是若有托管线程(非主线程),也无法干净的退出
//Application.ExitThread();//强制终止调用线程上的所有消息,同样面临其他线程无法正确退出的问题
}
}
}
配置文件
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<appSettings>
<add key="ConnStr" value="server=LYQ;database=charge_sys;uid=sa;pwd=123456"/>
<add key="DB" value="DAL"/>
<!--ConnStr 为 连接数据库 key为键值,引用字符DB,value代表引用的真正的内容-->
</appSettings>
</configuration>
数据访问接口层(IDAL)
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace IDAL
{
public interface LoginIDAL
{
DataTable SelectUser(Entity.UserInfo user);
}
}
数据访问工厂层(Factory)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;
using System.Reflection;
using IDAL;
namespace Factory
{
public class LoginFactory
{
//接收来自配置文件的数据
string strDB =ConfigurationManager.AppSettings["DB"];
//应用反射来获取DAL层操作
public IDAL.LoginIDAL CreatUser()
{
string ClassName = strDB + "." + "LoginDAL"; //这里的LoginDAL为D成的类名
return (IDAL.LoginIDAL)Assembly.Load(strDB).CreateInstance(ClassName); //反射加工厂的应用
}
}
}
外观层(Facade)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BLL;
namespace Facade
{
public class LoginFacade
{
public Boolean SelectUser(Entity.UserInfo user)
{
bool flag; //标记常量
LoginBLL userBLL = new LoginBLL(); //实例化
flag = userBLL.UserBLL(user); //跳转到BLL层
return flag;
}
}
}
实体层(Entity/Model)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Entity
{
public class UserInfo
{
//定义 用户ID 字段
private string userid;
public string UserID
{
get { return userid; }
set { userid = value; }
}
//定义 密码 字段
private string password;
public string Password
{
get { return password; }
set { password = value; }
}
}
}
数据访问层(DAL)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using Entity;
using IDAL;
namespace DAL
{
public class LoginDAL : LoginIDAL
{
public DataTable SelectUser(Entity.UserInfo user) //selectUser该方法即是IDAL接口的方法
{
//实例化数据操作类,进行数据查询,并获取返回值
SQLHelper sqlHelper = new SQLHelper(); //实例化 跳转到SQLHelper层
//声明一个SQL参数的数组 params可变数组
SqlParameter[] sqlParams = { new SqlParameter("@UserID", user.UserID), new SqlParameter("@PWD", user.Password) };
string sql = @"SELECT * FROM User_Info WHERE userID=@UserID AND PWD=@PWD"; //查的语句 用户名和密码相等并且状态为True
DataTable table = sqlHelper.ExecuteQuery(sql, sqlParams, CommandType.Text);
return table;
}
}
}
SQLHelper类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient; //这行代码必须有
using System.Data; //这行代码也必须有
using System.Configuration;
namespace DAL
{
//数据访问层——数据操作层
public class SQLHelper
{
#region 构造函数
//定义数据库连接操作,指定在数据库上操作的类型、定义数据库读取操作
private SqlConnection conn = null; //SqlConnection打开连接
private SqlCommand cmd = null; //SqlCommand对象允许你指定在数据库上执行的操作的类型,比如,你能够对数据库中的行数据执行select,insert,modify以及delete命令。
//SqlCommand对象能被用来支持断开连接数据管理的情况,可以只单独使用SqlCommand对象。
//也可以与SqlDataAdapter一起实现断开数据连接,实现操作数据库的应用程序读取只进的行流的方式
private SqlDataReader sdr = null;
//数据库连接
public SQLHelper()
{
string connStr = null;
connStr = ConfigurationManager.AppSettings["connStr"]; //ConnStr是配置文件里连接数据库的关键字,引用这一句话可以连接数据库
conn = new SqlConnection(connStr); //实例化一个连接
}
#endregion
#region SQL连接打开
private SqlConnection GetConn()
{
if (conn.State == ConnectionState.Closed) //如果连接状态为关闭
{
conn.Open(); //则打开
}
return conn;
}
#endregion
#region 非储存过程
/// <summary>
/// 执行不带参数的的 增删改 SQL语句或者存储过程
/// </summary>
/// <param name="cmdText">增删改SQL</param>
/// <param name="ct">命令类型</param>
/// <returns>返回受影响的行数</returns>
public int ExecuteNonQuery(string cmdText, CommandType ct)
//增删改SQL 命令类型
{
int res;
cmd = new SqlCommand(cmdText,GetConn()); //打开连接
cmd.CommandType = ct;
res = cmd.ExecuteNonQuery();
if (conn.State == ConnectionState.Open)
{
conn.Close(); //关闭数据库 与上面相对应
}
return res;
}
/// <summary>
/// 执行带参数的的 增删改 SQL语句或者存储过程
/// </summary>
/// <param name="cmdText">增删改SQL</param>
/// <param name="paras">要查询的参数</param>
/// <param name="ct">命令类型</param>
/// <returns>返回受影响的行数</returns>
public int ExecuteNonQuery(string cmdText,SqlParameter[] paras,CommandType ct)
{
int res;
using (cmd = new SqlCommand(cmdText,GetConn()))
{
cmd.CommandType = ct;
cmd.Parameters.AddRange(paras);
res = cmd.ExecuteNonQuery();
}
return res;
}
/// <summary>
/// 执行不带参数的 查询 SQL语句或存储过程
/// </summary>
/// <param name="cmdText">查询SQL语句或存储过程</param>
/// <param name="ct">命令类型</param>
/// <returns></returns>
public DataTable ExecuteQuery(string cmdText,CommandType ct)
{
DataTable dt = new DataTable();
cmd = new SqlCommand(cmdText, GetConn());
cmd.CommandType = ct;
using (sdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
dt.Load(sdr);
}
return dt;
}
/// <summary>
/// 执行带参数的 查询 SQL语句或存储过程
/// </summary>
/// <param name="cmdText">查询SQL语句或存储过程</param>
/// <param name="paras">参数集合</param>
/// <param name="ct">命令类型</param>
/// <returns></returns>
public DataTable ExecuteQuery(string cmdText,SqlParameter[]paras,CommandType ct)
{
DataTable dt = new DataTable();
cmd = new SqlCommand(cmdText, GetConn());
cmd.CommandType = ct;
cmd.Parameters.AddRange(paras);
using(sdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
dt.Load(sdr);
}
return dt;
}
#endregion
}
}
业务逻辑层(BLL)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Collections;
using IDAL;
using Factory;
namespace BLL
{
public class LoginBLL
{
//判断用户是否存在
public bool UserBLL(Entity.UserInfo user)
{
Factory.LoginFactory fact = new Factory.LoginFactory();//实例化登录工厂 跳转到Factory层 接收配置文件的数据,接收完成后再返回来
IDAL.LoginIDAL idal = fact.CreatUser();//通过工厂反射到IDAL层 跳转Factory 使用CreateUser的方法
DataTable table = idal.SelectUser(user); //接收D成的返回值 跳转至DAL层
bool flag;
if(table.Rows.Count==0) //返回Data类型,如果它的行数等于0,说明没有符合该账号密码的用户
{
flag = false;
}
else
{
flag = true;
}
return flag;
}
}
}
第一版的七层登录的代码较多是借鉴他人,生搬硬套,自己修改了部分,虽说大多都有注释,但还是有些代码不能理解,遇到的问题也较多,实现登录也较为棘手,好在后面通过探讨也解决了,真的,基础只是还是有待加强呀。