三层架构 - 需要一个例子

14

目前我正在使用单层架构进行工作。现在我想学习如何使用三层架构编写代码。请问能否提供一个简单的示例?


你可以在其他问题中看到我的答案:https://dev59.com/z2s05IYBdhLWcg3wANPj#7510526 - balexandre
http://www.youtube.com/watch?v=4n3xzK1Cfh0 - m.qayyum
9个回答

22
维基百科有一个很好的解释:多层架构:

'三层'是一种客户端-服务器体系结构,其中用户界面、功能过程逻辑(“业务规则”)、计算机数据存储和数据访问以独立模块的形式开发和维护,通常在不同的平台上。

alt text

Web开发应用

在Web开发领域中,“三层”通常用于指建立在三个层次上的网站,通常是电子商务网站:

  • 前端Web服务器提供静态内容,并可能提供一些缓存的动态内容。
  • 中间动态内容处理和生成级别的应用服务器,例如Java EE、ASP.net、PHP平台。
  • 后端数据库,包括数据集和管理和提供对数据访问的数据库管理系统或RDBMS软件。

我正在使用Visual Studio .NET 2005。我可以使用Visual SourceSafe吗?Rubens Farias先生 - Surya sasidhar
@Surya,虽然这与您最初的问题无关(它不算作一层),但是,是的,您应该使用某种源代码控制工具,如SourceSafe或TFS。 - Rubens Farias
最好不要使用SourceSafe,选择一个更新的SCM工具会是个好主意,特别是如果你刚开始学习。 - Paddy

15

这是我项目中的内容,不仅仅是传统的3层架构。

1.) Application.Infrastructure

  • 所有业务对象、业务对象集合、数据访问类和我的自定义属性和工具作为扩展方法的基类,通用验证框架。这决定了我的最终 .net 应用程序的整体行为组织。

2.) Application.DataModel

  • 数据库的类型化数据集。
  • TableAdapter 扩展以纳入事务和我可能需要的其他功能。

3.) Application.DataAccess

  • 数据访问类。
  • 实际上执行使用底层类型化数据集查询的数据库操作的地方。

4.) Application.DomainObjects

  • 业务对象和业务对象集合。
  • 枚举。

5.) Application.BusinessLayer

  • 提供从表示层可访问的管理器类。
  • HttpHandlers。
  • 我自己的 Page 基类。
  • 还有更多东西...

6.) Application.WebClientApplication.WindowsClient

  • 我的表示层
  • 引用 Application.BusinessLayer 和 Application.BusinessObjects。

Application.BusinessObjects 在整个应用程序中都被使用,并且在任何数据访问操作中以业务对象的形式返回或接收 [除了 Application.DataModel 和 Application.Infrastructure]。

我的所有查询都只在 Application.DataModel 中定义。

Application.DataAccess 返回或接收业务对象作为任何数据访问操作的一部分。使用反射属性创建业务对象。每个业务对象都标记有映射到数据库目标表的属性,业务对象内的属性使用属性标记映射到相应的数据表列。

我的验证框架允许我使用指定的 ValidationAttribute 验证每个字段。

我的框架大量使用属性来自动化大多数繁琐任务,例如映射和验证。我还可以将新功能作为框架中的新方面。

在我的应用程序中,样例业务对象看起来像这样。

User.cs

[TableMapping("Users")]
public class User : EntityBase
{
    #region Constructor(s)
    public AppUser()
    {
        BookCollection = new BookCollection();
    }
    #endregion

    #region Properties

    #region Default Properties - Direct Field Mapping using DataFieldMappingAttribute

    private System.Int32 _UserId;

    private System.String _FirstName;
    private System.String _LastName;
    private System.String _UserName;
    private System.Boolean _IsActive;

    [DataFieldMapping("UserID")]
    [DataObjectFieldAttribute(true, true, false)]
    [NotNullOrEmpty(Message = "UserID From Users Table Is Required.")]
    public override int Id
    {
        get
        {
            return _UserId;
        }
        set
        {
            _UserId = value;
        }
    }

    [DataFieldMapping("UserName")]
    [Searchable]
    [NotNullOrEmpty(Message = "Username Is Required.")]
    public string UserName
    {
        get
        {
            return _UserName;
        }
        set
        {
            _UserName = value;
        }
    }

    [DataFieldMapping("FirstName")]
    [Searchable]
    public string FirstName
    {
        get
        {
            return _FirstName;
        }
        set
        {
            _FirstName = value;
        }
    }

    [DataFieldMapping("LastName")]
    [Searchable]
    public string LastName
    {
        get
        {
            return _LastName;
        }
        set
        {
            _LastName = value;
        }
    }

    [DataFieldMapping("IsActive")]
    public bool IsActive
    {
        get
        {
            return _IsActive;
        }
        set
        {
            _IsActive = value;
        }
    }

    #region One-To-Many Mappings
    public BookCollection Books { get; set; }

    #endregion

    #region Derived Properties
    public string FullName { get { return this.FirstName + " " + this.LastName; } }

    #endregion

    #endregion

    public override bool Validate()
    {
        bool baseValid = base.Validate();
        bool localValid = Books.Validate();
        return baseValid && localValid;
    }
}

BookCollection.cs

/// <summary>
/// The BookCollection class is designed to work with lists of instances of Book.
/// </summary>
public class BookCollection : EntityCollectionBase<Book>
{
    /// <summary>
    /// Initializes a new instance of the BookCollection class.
    /// </summary>
    public BookCollection()
    {
    }

    /// <summary>
    /// Initializes a new instance of the BookCollection class.
    /// </summary>
    public BookCollection (IList<Book> initialList)
        : base(initialList)
    {
    }
}

10
“tier”指的是软件堆栈中的“层”,而“tier”一词更适用于描述系统的物理组件。如果您正在使用ASP.NET,则可能已经拥有一个“3层”系统 -
  • 浏览器显示网页
  • IIS服务器托管您的应用程序
  • 带有数据库的数据库服务器
但是,您可能会将所有代码放入单个软件“层”中 - 具体来说,是aspx页面的代码后面文件。您需要从单层过渡到三层方法。经典的“3层”软件架构包括以下内容 -
  1. 演示层

  2. 业务逻辑层(BLL)

  3. 数据访问层(DAL)

alt text (来源:asp.net) 对于典型的ASP.NET应用程序,您可以按如下方式应用它。首先,您创建一个包含数据库访问对象的LINQ2SQL文件(.dbml)。这是您的数据访问层(DAL)。
下一步,您可以创建一个DLL来包含您的业务逻辑层(BLL)。此层将通过数据访问层(DAL)访问数据库,根据需要进行操作,然后通过简单的接口公开访问。例如,如果您的应用程序显示客户列表,则BLL可能有一个名为GetClientList()的公共函数,返回客户列表。
最后,您需要设置代码后台文件来实例化BLL并将其连接到接口组件。这就是您的演示层(Presentation Layer)。例如,它可能会使用从GetClientList()函数返回的数据绑定到Web表单上的数据网格中。想法是尽可能地使表示层薄。
这似乎有点啰嗦,但一旦您做过几次,就会非常简单。您会发现,像这样分离您的应用程序将使其更容易维护,因为关注点的分离导致代码更清晰。您还会发现,升级甚至替换表示层都会变得更加容易,因为它包含很少的智能。最后,您将达到一个可以轻松在新应用程序中消耗的非常有用的BLL库的数量,从而极大地提高了生产力。

5

展示层:包括与用户界面相关的所有内容。 (用户看到了什么)

业务层:与应用程序逻辑相关的所有内容(来自展示层的信息如何处理)

数据层:提供底层数据源的抽象层(来自/去往业务层的信息存储在哪里和如何存储)

每个层次应尽可能少地了解其他层次,并且它应该是自上而下的方法:

  • 数据层不应知道业务和展示层
  • 业务层应了解数据但不了解展示
  • 展示层应了解业务但不了解数据

简单的例子:

网站:

  • 展示:所有图形元素,用于插入数据的字段,菜单,图片等。
  • 业务:关于数据的所有约束(唯一名称,无符号名称,有效日期等),操作业务对象的方法(创建新用户,添加新订单等)
  • 数据:访问底层数据库的方法。

5
3层架构在不同的上下文中可能有不同的含义。一般来说,它意味着应用程序的职责被划分为不同的层次。通常,3层指的是:

  • 表示层(实际的用户界面)
  • 逻辑层(应用程序/业务逻辑)
  • 数据层(数据库、数据存储)

具体细节因应用而异。

维基百科有一个很好的概述:http://en.wikipedia.org/wiki/Multitier_architecture

一个简单的例子就是一个典型的业务应用:

  • 表示层:浏览器或者桌面客户端
  • 逻辑层:业务逻辑,通常在应用服务器中(基于J2EE、ASP.NET或其他技术实现)
  • 数据层:一个数据库,通常是MySQL或Oracle等关系型数据库

2

3层架构通常包括以下组件:

  1. 客户端浏览器
  2. 托管ASP.NET应用程序的Web服务器
  3. 一些后端存储,例如由ASP.NET应用程序访问的数据库

因此,要回答您关于如何为3层架构编写代码的问题,您需要开发一个与数据存储通信的ASP.NET应用程序。


如果您的网站前面有防火墙/负载均衡器,甚至可以说您拥有四层架构。;^) - Toad
通常情况下,物理网络设备不被计算在内。即使客户端浏览器、托管应用程序的 Web 服务器和数据库都在同一台物理机器上,它仍然是一个三层架构。 - Darin Dimitrov
1
你是否认为浏览器是架构的一部分?为什么不呢:1. Web服务器提供静态内容或渲染模板;2. 服务器端业务逻辑和动态内容生成;3. 数据库。 - Daniel Vassallo
实际上,在传统模型中,分层方式会有所不同。客户端浏览器和Web服务器都是演示层的一部分(因为Web服务器创建HTML和CSS)。因此,Web服务器上的应用程序实际上既是业务层也是演示层的一部分。但当然,术语始终存在争议... - sleske
如果我在业务层编写连接类,如何在用户界面层获取Web.config文件中的连接字符串? - Surya sasidhar
将其放在数据访问层类的构造函数中。当该类被实例化(在接口层),您可以从web.config中传递值。 - Darin Dimitrov

0
connection class
-----------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Web .UI.WebControls ;
/// <summary>
/// Summary description for conn
/// </summary>
namespace apm_conn
{
    public class conn
    {
        public SqlConnection getcon()
        {
            SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["connect"].ConnectionString );
            if (con.State == ConnectionState.Closed)
            {
                con.Open();
            }
            return con;
        }
        #region execute command
        public string  Executecommand(SqlParameter []sqlparm,string sp)
        {
            string r_val = "";
            try
            {

                SqlConnection con = new SqlConnection();
                con = getcon();
                SqlCommand cmd = new SqlCommand();
                cmd.Connection = con;
                cmd.CommandText = sp;
                cmd.CommandType = CommandType.StoredProcedure;
                foreach (SqlParameter loopvar_parm in sqlparm)
                {
                    cmd.Parameters.Add(loopvar_parm);

                }
                cmd.Parameters.Add("@Var_Output", SqlDbType.VarChar, 20).Direction = ParameterDirection.Output;
                cmd.ExecuteNonQuery();
                r_val = (string)cmd.Parameters["@Var_Output"].Value;
                con.Close();
            }
            catch { }
            return r_val;

        }

        #endregion
        #region Execute Dataset
         public DataSet ExeccuteDataset(SqlParameter[] sqlParm, string sp)
    {
        DataSet ds = new DataSet();
        try
        {
            SqlConnection con = new SqlConnection();
            con = getConn();
            SqlCommand cmd = new SqlCommand();
            cmd.Connection = con;
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.CommandText = sp;
            foreach (SqlParameter LoopVar_param in sqlParm)
            {
                cmd.Parameters.Add(LoopVar_param);
            }
            cmd.ExecuteNonQuery();
            SqlDataAdapter da = new SqlDataAdapter(cmd);
            da.Fill(ds);

        }
        catch
        { }
        return ds;
    }
        #endregion
        #region grid
        public void Bindgrid(DataSet ds,GridView g)
        {
            try
            {
                g.DataSource = ds.Tables[0];
                g.DataBind();

            }
            catch { }
        }
        #endregion
        #region Dropdownlist
        public void Binddropdown(DropDownList dl,DataSet ds,string text,string value)
        {
            try
            {
                dl.DataSource = ds.Tables[0];
                dl.DataTextField = text;
                dl.DataValueField = value;
                dl.DataBind();
            }
            catch
            {}

        }
        #endregion
        public conn()
        {
            //
            // TODO: Add constructor logic here
            //
        }
    }
}




dal
---------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using apm_conn;
using System.Data.SqlClient;
using apm_ent;

/// <summary>
/// Summary description for Class1
/// </summary>
namespace apm_dal
{
    public class dal
    {
        conn ob_conn = new conn();
        public dal()
        {
            //
            // TODO: Add constructor logic here
            //
        }
        public string insert(ent obj_ent)
        {
            SqlParameter[] sqlparm =
        {
            new SqlParameter ("@Var_Action",obj_ent.Var_Action),
            new SqlParameter ("@Int_Id",obj_ent.Int_Id ),
             new SqlParameter ("@Var_Product",obj_ent.Var_Product ),
              new SqlParameter ("@Dc_Price",obj_ent.Var_Price ),
              new SqlParameter ("@Int_Stat",obj_ent.Int_Stat ),

                               };
            return ob_conn.Executecommand(sqlparm, "Proc_product");
        }
        public string ins(ent obj_ent)
        {
            SqlParameter[] parm =
        {
            new SqlParameter ("@Var_Action",obj_ent .Var_Action),
            new SqlParameter ("@Int_Id",obj_ent .Int_Id),
                            };
            return ob_conn.Executecommand(parm, "Proc_product");
        }
    }

}

bal
-------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using apm_ent;
using apm_dal;

/// <summary>
/// Summary description for bal
/// </summary>
namespace apm_Bal
{

    public class bal
    {
        dal ob_dal = new dal();
        string r_val = "";
        public bal()
        {
            //
            // TODO: Add constructor logic here
            //
        }
        public string insert(ent obj_ent)
        {
            return ob_dal.insert(obj_ent);
        }
    }
}



Ent
------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

/// <summary>
/// Summary description for ent
/// </summary>
namespace apm_ent
{
    public class ent
    {
        public ent()
        {
            //
            // TODO: Add constructor logic here
            //
        }
        #region Ent
        public int Int_Id { get; set; }
        public string  Var_Action { get; set; }
        public string Var_Product { get; set; }
        public decimal  Var_Price { get; set; }
        public int Int_Stat { get; set; }
        #endregion
    }
}



page code
--------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using apm_conn;
using apm_ent;
using apm_Bal;
using apm_conn;
public partial class _Default : System.Web.UI.Page
{
    conn obj_conn = new conn();
    ent obj_ent = new ent();
    bal obj_bal = new bal();
    string r_val = "";
    protected void Page_Load(object sender, EventArgs e)
    {

    }
    protected void btnsub_Click(object sender, EventArgs e)
    {
        obj_ent.Var_Action = "INS";
        obj_ent.Var_Product = txtproduct.Text;
        obj_ent.Var_Price = Convert.ToDecimal (txtprice.Text);
        r_val = obj_bal.insert(obj_ent);
        if (r_val == "1")
        {
            Response.Write("<script>alert('Inserted Sucessfully')</script>");
        }
    }
}

0
一个好的教程,附有完整的源代码下载以及精心编写的分层应用程序可以在这里找到:

http://nerddinnerbook.s3.amazonaws.com/Intro.htm

这不是关于分层架构的教程,但它是一个写得很好的应用程序,并提供了一些洞见,让你了解为什么要考虑这种架构。
此外,正如上面简要提到的那样,这是关于将逻辑/存储/表示代码分开,因此如果您必须更改其中之一(例如从asp.net前端更改为桌面应用程序),那么这并不难做到。

0

三层(层)是一种客户端服务器架构,用户界面、业务流程(业务规则)和数据存储以及数据访问被开发和维护为独立的模块或者通常在单独的平台上。

基本上,有三个层次:

  • 第1层(表示层、GUI层)
  • 第2层(业务对象、业务逻辑层)
  • 第3层(数据访问层)。这些层可以分别进行开发和测试。

将代码分为三个层次的必要性是什么?将用户界面与业务逻辑和数据库访问分离具有许多优点。以下是其中一些优点:

业务逻辑组件的可重用性可以快速开发。假设我们有一个模块,处理系统中客户的添加、更新、删除和查找。由于该组件已经开发和测试完成,我们可以在任何其他涉及维护客户的项目中使用它。
系统的转换很容易。由于业务逻辑与数据访问层分离,更改数据访问层不会对业务逻辑模块产生太大影响。例如,如果我们从 SQL Server 数据存储转移到 Oracle,业务层组件和 GUI 组件都不需要进行任何更改。
系统的变更管理很容易。例如,如果业务逻辑发生了微小的变化,我们不必在每个用户的个人计算机上安装整个系统。例如,如果 GST(税)从 10% 变为 15%,我们只需要更新业务逻辑组件,而不会影响用户,并且没有任何停机时间。
拥有单独的功能服务器允许应用程序专家并行开发各个层级的功能。
提供更灵活的资源分配。通过使功能服务器将数据剥离到发送给客户端之前所需的精确结构,可以减少网络流量。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接