65.9K
CodeProject 正在变化。 阅读更多。
Home

构建框架 - 第二部分 (实用工具)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (16投票s)

2014年4月1日

CPOL

10分钟阅读

viewsIcon

31556

downloadIcon

4

关于创建可复用框架的第二篇文章

相关文章

构建一个框架 - 第一部分 (DAL)

引言

第一篇文章处理了数据库连接(众多可能性之一)以及如何使其可重用。本文在第一部分的基础上,将探讨其他经常被使用且经常分散在整个应用程序(以及应用程序之间)的功能。

再次注意,做事情的方法有很多种,这只是其中之一。本文更多地是关于组织功能,而不是关于代码。

选择最适合你的。

日志记录

在许多情况下,日志记录仍然以非常不同的方式使用 ASCII 文件进行。日志条目可以是单行或多行块,可能存在循环文件系统(例如 log1.txt 到 log3.txt,当 log3.txt 满了后返回 log1.txt),文件扩展名的数量可能非常庞大,从 *.txt 到 *.log 再到开发人员想出的任何其他扩展名。更糟糕的是,一些日志记录是针对二进制文件进行的。

人们经常问应用程序中应该记录什么。这真的取决于你,但这里有一些提示和需要考虑的事情:

  • 始终记录异常
  • SQL 查询(如果参数化,也要提供值)
  • * 每次函数调用及其传递的参数。
  • * 每次函数返回(如果可能)
  • 代码中的关键点,例如计算中的里程碑、某个工作流过程的开始/结束等。
  • 用户的登录/注销
  • 用户的重要决定
  • 与其他应用程序的通信(发送/接收)
  • 始终想象你处于一个需要仅根据日志来解决问题的境地。
  • 将日志记录到数据库的优点是,你可以为其开发一个查看器或监视器,并以任何你想要的方式显示它(例如,作为手机上的网页,完全颜色编码)。你还可以从中为你的经理生成报告,并构建易于使用的过滤器,以便只查看你当时需要的部分内容。因此,我的建议是,如果可以的话,使用数据库(而不是文件)。
  • 日志可能来自其他系统,例如 Linux。
  • 如果使用文件,请限制文件大小。我建议 2 MB 到 5 MB,最大不超过 10 MB。
  • 在循环内部记录日志时要小心,并记住有“太多”这种事情!

* - 并非绝对必要。事实上,如果你在这里添加日志记录,它可能更像是一个调试过程,而不是一个操作过程。

设置

设置与日志记录有些相似,因为它们通常最终存储在本地计算机上的文件中,甚至是多个文件中。同样,有多个版本在使用:XML 文件、.NET 配置文件(web.config、app.config)、*.ini 文件、二进制文件等...

设置主要限于键/值对,因此我也选择将它们添加到数据库中。甚至可以添加(链接)列表(例如用于填充组合框),但本文不讨论这些。

设置中应该包含什么?这真的取决于你,但我通常会添加路径、URL、配置参数等...

在这里你可以编写一个配置应用程序,允许轻松更改这些设置。它的优点是你不必开始寻找配置文件,希望能快速找到它,并且确认那个文件确实在使用中,并且设置没有被其他文件覆盖。

背景

我创建了这个日志模块,因为我们以前有一个大型系统,许多应用程序协同工作,通过数据库、UDP、TCP、消息队列等方式发送信息。在调试时,我们经常最终打开两到四个日志文件,试图导航不同的格式并比较在不同机器上生成且时钟不同步的日志的时间戳。

来到另一家公司后,我有一些重新设计东西的自由,我决定创建一个模块来将日志记录到数据库。日志上的时间戳是数据库的时间戳,因此所有时间都同步,你可以使用 SQL 过滤出你需要的内容。此外,你只有一个来源可以查找。

对于设置和日志记录,你都可以使用 .NET 的企业库,它带有一个配置程序。然而,配置程序(恕我直言)不是很用户友好,日志记录需要一些摸索,尽管它可以与 ASCII 文件或 SQL-Server 一起使用。如果你有另一个数据库(例如 Oracle),那你会遇到一些麻烦,最好放弃这个东西。

需要注意的一件重要事情,我认为这两种情况都适用,那就是当应用程序崩溃时,你会有日志来解释这次崩溃。在许多情况下,应用程序在日志写入之前就崩溃了。

关于设计的一些快速说明

n-tier layers

这是一个非常基本的设计,适用于中小型桌面或 Web 应用程序。在大型应用程序中,层可以拆分,例如业务对象层和业务逻辑层,或者多个 DAL 组件,其中一个部分与数据库通信,另一个部分与 Web 服务通信,还有一个部分读写文件。另一方面,接口或服务等其他应用程序没有 GUI 层。

本文讨论了实用工具层。这一层的目标是在各层之间提供功能。这里讨论了日志记录和设置,但其他功能也适用于这一层,例如用户访问权限、你需要的转换器(例如公历日期到儒略日期等)。

在我的案例中,我将 DAL 和实用工具框架放在一个程序集中,这是有意义的,因为功能很小,而且实用工具使用了 DAL 组件。由于它是一个程序集,它们之间没有依赖关系。如果框架变得很大,你可能需要决定将项目拆分为多个程序集。

使用代码

该框架现在包含两个模块

  1. DAL
  2. 实用工具

对于 Dal 组件,我参考我的第一篇文章。实用工具组件包括

  • Log
  • 设置

但这仅仅是一个例子。在我们自己的框架中,我还添加了儒略/公历日期转换器等。在 LINQ 出现之前,我也有一个 XML 模块,等等...

注意:尽管我称之为实用工具,但我见过它有许多不同的名称:utils、tools、bricks 等...

日志模块主要由 3 个类组成。一个是实际的日志类,它接收一些传递给它的设置,并“分派”给 Log2File 和/或 Log2Database 类。

让我们首先看看日志记录类。

Log

要创建一个新的日志实例,你需要使用静态的“GetInstance”方法

 //create a log object that logs to file
 Log log = Log.GetInstance(@"C:\temp\logtest.txt", ""); 

日志带有四个设置

 public string Path{}
 public string ConnectionString{}
 public Framework.Dal.Enums.provider DBProvider {}
 public int MaximumFileSize{} 

可用于覆盖默认设置或更改日志文件或日志数据库。

要实际记录条目,你可以调用 WriteLog 方法

 public void WriteLog(Enums.LogType type, string text, string applicationormodulename, Enums.Actor actor, Enums.Medium medium){} 

如果你愿意,可以选择创建一个类来保存这些参数作为属性,然后直接将该类作为参数传递。

WriteLog 方法将检查需要记录的内容,并将参数传递给 Log2File 和/或 Log2Database 类,这些类不能直接调用。

请注意,我们可以将 Enums.Medium.File|Enums.Medium.Database 作为最后一个参数传递,以同时记录到两种介质。这是因为 Medium 的枚举具有 Flags 属性

[Flags]
public enum Medium{
    None = 0,
    File = 1,
    Database = 2
    /*next should be 4, 8, 16, ..; */
}; 

Log2File 类有一些显著的特点。

  • 它会检查文件大小并在必要时重新创建文件。(循环使用,但只有一个文件)
  • 它使用lock 功能使过程线程安全。
  • 它不使用数据库时间(当记录到两种介质时要小心,因为机器可能不同步!)
  • 它会自动为你添加日期时间,因此你不需要在日志文本中提供它。
  • 它每条日志使用一行(你可以在文本中添加换行符),但你可以通过更改 Write 方法来以任何你想要的方式格式化文件。
  • 该类是internal 的,因此你不能从程序集外部使用它。(你需要为此使用 Log 类)

Log2Database 有一些显著的特点。

  • 在这种情况下,Write 函数使用 OleDb 连接到 Oracle 实例,但你可以修改它以允许连接到多个数据库和访问提供程序(MySql、SQL-Server、OleDb、Odp.Net、ODBC 等)
  • 不需要提供日志的日期时间戳,而是使用数据库的当前时间戳。(这对于每个数据库都不同,对于 Oracle 来说是 systimestamp),请确保将“粒度”设置得足够高(在定义列时也是如此!),这意味着达到毫秒级别(或者基本上尽可能高)
  • 当然,你所记录的表需要存在。
  • 该类是internal 的,因此你不能从程序集外部使用它。(你需要为此使用 Log 类)

最后,对日志的一些属性进行解释

  1. LogType
    这表示日志的类型。信息/错误/警告/...
  2. 文本
    日志文本
  3. 应用程序或模块名称
    生成日志的应用程序名称。如果您使用多个应用程序记录到同一位置,这一点很重要。
  4. Actor
    用户或系统。这可以用来区分用户操作或决策(例如在工作流过程中)或机器日志(自动化系统、接口等)
  5. 媒体
    日志记录的介质,如文件、数据库、系统事件日志、你的 iPad、短信等。

在我目前使用的 Java 版本中,我还添加了严重性调试级别,后者在你处于调试或问题解决模式时非常有用,但你希望将操作日志记录保持在较低水平。

设置

设置类非常非常简单。我添加它的原因是,当我还是一个初级开发人员时,我很难在代码的不同级别之间获取信息。所以你会做你可能以前见过的事情:创建一个类,其中包含一堆可供所有人访问的全局变量。但这不是很干净,是吗?

我发现最简单的方法是这样的

 public class Settings {
    private static Dictionary<string, string> settings;
    private static void Load(){
        /*    Load settings from database and / or file.
         *    hardcoded for testing
         */
        settings = new Dictionary<string,string>();
        settings.Add("database", "PostgreSQL");
        settings.Add("username", "John Doe");
        settings.Add("path", @"C:\temp\");
    }

    /// <summary>
    /// Gets the value associated with a certain key.
    /// </summary>
    /// <param name="key">The key of the setting.</param>
    /// <returns>The value of the setting.</returns>
    public static string GetSetting(string key){
        string setting = "";
        if(settings == null){
            Load();
        }            //end if
        if(settings != null && settings.ContainsKey(key)){
            setting = settings[key];
        }            //end if
        return setting;
    }
} 

首次访问设置时会启动 Load 方法。在我的案例中,这涉及读取数据库参数的配置并读取设置表。在这里,键/值对始终是字符串,并且此类只处理键/值对。但是,你可以根据自己的需求进行扩展。此外,你还可以从文件或数据库加载(链接)列表以填充组合框,对设置进行分类,将它们分配给用户,使用覆盖类别等。

如果您将设置添加到数据库中,可以记住以下几点:

  • 设置主要是键/值对,因此您至少需要这两个列。
  • 设置可能需要分配一个或多个类别。
  • 设置基本上有两种类型:系统设置和用户设置。普通用户不应访问系统设置。用户设置在不同用户之间可能不同。
  • 不同的应用程序可以有相同的键,但值不同。

关注点

  • 如果您的设置在数据库中,那么数据库连接是硬编码的吗?
    答案是“不”,数据库凭据仍然存在于(最好是)标准配置文件中,这也是唯一存在的东西。(WCF 是一个例外)
    您*可以*在代码中“硬编码”开发/测试/生产数据库凭据,然后只设置一个参数,例如 Dev / Test / Prod。
    所以我同意,将设置放在数据库中并不能完全解决设置文件的问题,但它确实简化了事情。
  • 如果我从头开始构建日志,我可能会重新设计它,使用抽象基类。
    此外,如果您选择只使用文件或只使用数据库,则可以大大简化此过程。另一方面,您可能需要记录到 Windows 事件日志等。在这种情况下,您可以添加一个模块。

结语

我敢肯定,阅读本文的*某些*人会不同意这种工作方式,但我想强调这只是一种做事方式。事实上,本文更多地是关于如何在代码中建立结构并避免一些陷阱,而不是实际的代码。尽管这些类多年来有所变化,但基本原则保持不变。

如果您对此感兴趣,请不要盲目复制/粘贴代码。使用任何对您有用的东西并根据您自己的需求进行调整。

历史

版本 1.0 (2014 年 4 月)

© . All rights reserved.