Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >创建可维护和可测试的 Windows 窗体应用程序的 10 种方法(译)

创建可维护和可测试的 Windows 窗体应用程序的 10 种方法(译)

作者头像
沙漠尽头的狼
发布于 2021-12-15 08:14:18
发布于 2021-12-15 08:14:18
1.4K00
代码可运行
举报
文章被收录于专栏:Dotnet9Dotnet9
运行总次数:0
代码可运行

我遇到的大多数 Windows 窗体应用程序都不存在或单元测试覆盖率极低。而且它们通常也很难维护,项目中各种 Form 类的代码背后有数百甚至数千行代码,但它不必是这样。仅仅因为 Windows 窗体是一项“遗留”技术,并不意味着你注定会造成无法维护的混乱。下面是创建可维护和可测试的 Windows 窗体应用程序的十个技巧。

1. 用用户控件隔离你的用户界面

首先,避免在一个表单上放置太多控件。通常,你的应用程序的主要形式可以分解为逻辑区域(我们可以称之为“视图”)。如果将这些区域中的每个区域的控件放入它们自己的容器中,那么你自己的生活就会变得更加轻松,而在 Windows 窗体中,最简单的方法是使用用户控件。因此,如果你有一个资源管理器样式的应用程序,左侧是树视图,右侧是详细信息视图,则将 TreeView 放入其自己的 UserControl,并为每个可能的右侧视图创建一个 UserControl。同样,如果你有选项卡控件,请为选项卡控件中的每个页面创建一个单独的 UserControl。

这样做不仅可以防止你的类变得难以管理,而且还可以调整大小和设置Tab 键顺序等,使任务变得更加简单。它还允许你在必要时轻松地一次性禁用用户界面的整个部分。你还会发现,当你将用户界面分解为包含逻辑分组控件的较小 UserControl 时,重新设计应用程序的 UI 布局会变得更加容易。

2. 将非 UI 代码排除在后面的代码之外

在 Windows 窗体应用程序中,你总是会在窗体背后的代码中找到访问网络、数据库文件系统的代码。这严重违反了“单一责任原则”。你的 Form 或 UserControl 类的重点应该只是用户界面。因此,当你检测到背后的代码中存在与 UI 无关的代码时,请将其重构为具有单一职责的类。因此,你可以创建一个 PreferencesManager 类,或者一个负责调用特定 Web 服务的类。然后可以将这些类作为依赖项注入到你的 UI 组件中(尽管这只是第一步——我们可以进一步扩展这个想法,我们很快就会看到)。

3. 用接口创建被动视图

一种特别有用的技术是使你创建的每个窗体和用户控件都实现一个视图接口。此接口应包含允许设置和检索视图中控件的状态和内容的属性。它还可能包括报告用户交互的事件,例如单击按钮或移动滑块。目标是这些视图接口的实现是完全被动的。理想情况下,你的 Forms 和 UserControls 背后的代码中不应该有任何条件逻辑。

下面是一个用于新用户条目视图的视图接口示例。这个视图的实现应该是微不足道的。任何业务逻辑都不属于后面的代码(我们接下来将讨论它属于哪里)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
interface INewUserView
{
    string FirstName { get; set; }
    string LastName { get; set; }
    event EventHandler SaveClicked;
}

通过确保你的视图实现尽可能简单,你将能够最大程度地迁移到替代 UI 框架(如 WPF),因为你唯一需要做的就是在新技术中重新创建视图。所有其他代码都可以重复使用。

4.使用presenters控制视图

因此,如果你已将所有视图设为被动并实现接口,则你需要一些能够实现应用程序业务逻辑并控制视图的东西。我们可以称这些为“presenter”类。这是称为“模型视图演示者”或 MVP 的模式。

在模型视图展示器中,你的视图是完全被动的,展示器会指示视图显示哪些数据。还允许视图与演示者通信。在我上面的示例中,它通过引发事件来实现,但通常使用这种模式,你的视图可以直接调用演示者。

绝对不允许视图开始直接操作模型(包括你的业务实体、数据库层等)。如果你遵循 MVP 模式,你的应用程序中的所有业务逻辑都可以轻松测试,因为它位于 Presenter 或其他非 UI 类中。

5. 为错误报告创建服务

通常,你的演示者类需要显示错误消息。但不要只是将 MessageBox.Show 放入非 UI 类中。你将使该方法无法进行单元测试。而是创建一个服务(比如 IErrorDisplayService),你的演示者可以在需要报告问题时调用该服务。这使你的演示者单元保持可测试性,并且还提供了更改将来向用户呈现错误的方式的灵活性。

6. 使用命令模式

如果你的应用程序包含一个带有大量按钮供用户单击的工具栏,则命令模式可能非常适合。命令模式规定你为每个命令创建一个类。这有很大的好处,可以将你的代码分成小类,每个小类都有一个责任。它还允许你集中处理与特定命令有关的所有事情。是否应该启用该命令?它应该是可见的吗?它的工具提示和快捷键是什么?它是否需要特定的特权或许可才能执行?命令运行时抛出的异常应该如何处理?

命令模式允许你标准化处理应用程序中所有命令所共有的每个问题的方式。你的命令对象将有一个 Execute 方法,该方法实际上包含为该命令执行所需行为的代码。在许多情况下,这将涉及调用其他对象和业务服务,因此你需要将它们作为依赖项注入到命令对象中。你的命令对象本身应该可以(并且直接)进行单元测试。

7. 使用 IoC 容器管理依赖项

如果你正在使用 Presenter 类和 Command 类,那么你可能会发现它们所依赖的类的数量随着时间的推移而增长。这是Unity或StructureMap等控制反转容器真正可以帮助你的地方。无论它们具有多少级别的依赖关系,它们都允许你轻松构建视图和演示器。

8. 使用事件聚合器模式

另一种在 Windows 窗体应用程序中非常有用的设计模式是事件聚合器模式(有时也称为“信使”或“事件总线”)。这是一种模式,其中事件的引发者和事件的处理者根本不需要相互耦合。当你的代码中发生需要在其他地方处理的“事件”时,只需向事件聚合器发布一条消息即可。然后需要响应该消息的代码可以订阅和处理它,而无需担心是谁提出的。

例如,你发送一条“请求帮助”消息,其中包含用户当前在 UI 中的位置的详细信息。然后另一个服务处理该消息并确保在 Web 浏览器中启动帮助文档中的正确页面。另一个例子是导航。如果你的应用程序有多个屏幕,则可以将“导航”消息发布到事件聚合器,然后订阅者可以通过确保新屏幕显示在用户界面中来响应该消息。

除了从根本上分离事件的发布者和订阅者之外,事件聚合器还具有创建极易进行单元测试的代码的巨大好处。

9. 使用 Async 和 Await 进行线程处理

如果你的目标是 .NET 4 及更高版本并使用 Visual Studio 12 或更高版本,请不要忘记你可以使用新的 async 和 await 关键字,这将大大简化应用程序中的任何线程代码,并自动处理回送后台任务完成后进入 UI 线程。它们还极大地简化了跨多个链式后台任务的异常处理。它们非常适合 Windows 窗体应用程序,如果你还没有的话,非常值得一试。

10.不要太晚

可以将我上面描述的所有模式和技术改造为现有的 Windows 窗体应用程序,但我可以从痛苦的经验告诉你,这可能需要大量工作,尤其是当窗体背后的代码达到数千行时。如果你开始使用 MVP、事件聚合器和命令模式等模式构建应用程序,你会发现随着它们变得越来越大,维护起来会少很多痛苦。你还可以对所有业务逻辑进行单元测试,这对于持续的可维护性至关重要。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Dotnet9 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
访问网站首页 index.php,跟着执行流程走一遍
访问网站首页 index.php,跟着执行流程走一遍,细节不深究,碰到不太明白的变量,直接var_dump()输出看看: 1. index.php——首页入口页面,版本判定、是否开启调试、引入 ThinkPHP框架 路径:‘./index.php‘ 判断PHP版本需是5.3.0以上:version_compare(PHP_VERSION,‘5.3.0‘,‘<‘); 系统调试设置:define(‘APP_DEBUG‘, true ); 应用目录设置:define ( ‘APP_PATH‘, ‘./Application/‘ ); 缓存目录设置:define ( ‘RUNTIME_PATH‘, ‘./Runtime/‘ ); 引入ThinkPHP:require ‘./ThinkPHP/ThinkPHP.php‘; 2. ThinkPHP.php——ThinkPHP框架的入口文件,定义各种常量、判断系统环境,初始化应用 路径:‘./ThinkPHP/ThinkPHP.php‘ 定义常量:版本号 THINK_VERSION、URL 模式定义(4 种模式)、类文件后缀 EXT、是否为SAE 环境、常用的系统路径常量(如Think类库目录、应用公共目录、缓存目录、配置目录等); 引入核心类Think.class.php:require CORE_PATH.‘Think‘.EXT; 应用初始化:Think\Think::start(); //命名空间\类名::方法(); //这里用到了命名空间 3. Think.class.php——框架的核心类,初始化应用程序,加载配置、类库,错误和异常处理,实例化对象 路径:‘./ThinkPHP/Library/Think/Think.class.php‘ 声明:Think\Think start()方法:加载需用的类、配置、语言包,是否需要缓存,运行应用 ①设定方法: spl_autoload_register(‘Think\Think::autoload‘); 自动加载类的方法,以及一些错误异常处理方法; ②分布式存储类初始化,用于读取、写入、删除文件;Storage::connect(STORAGE_TYPE); ③开发模式不缓存加载的核心类文件$runtimefile,用户模式将所有需引用的类并到同一个文件中缓存,加快后续访问速度。 ④加载应用的配置文件、需要的函数和类文件、行文扩展等文件路径的数组$mode;include ‘./ThinkPHP/Mode/common.php‘ ⑤循环加载处理$mode 数组中的各路径的文件; ⑥检查应用目录结构是否存在,不存在则会默认生成目录结构;(这个针对ThinkPHP新建一个应用,首次访问时使用) ⑦开始运行应用 App::run(); 即:‘./ThinkPHP/Library/Think/App.class.php‘ 4. App.class.php——加载公共文件配置、URL解析、调用对应的控制器方法 路径:‘./ThinkPHP/Library/Think/App.class.php‘ 声明:Think\App run()方法: ①App::init(); load_ext_file 加载应用的公共文件(./Application/Common/Common/)配置 (./Application/Common/Conf) Think\Dispatcher::dispatch(); URL解析,获取控制器 index、方法 index ②App::exec(); 执行应用程序,及新建控制器HomeConstroller 的实例,即对象; 创建控制器实例:$module = controller(CONTROLLER_NAME,CONTROLLER_PATH); 即=new IndexController(); 利用 php 反 射 机 制 获 取 action 方 法 对 象 , $method = new \ReflectionMethod($module, $action); 执行这个方法:$method->invoke($module); //无参数时执行,访问首页默认执行这个 $method->invokeArgs($module,$args); //有参数时执行; 以上即执行了控制器 ./Application/Hom
PM吃瓜
2019/08/13
4K0
Github-ThinkPHP 2.X 任意代码执行
ThinkPHP 3.0 版本因为 Lite 模式下没有修复该漏洞,也存在这个漏洞。所以先来看看preg_replace这个函数,这个函数是个替换函数,而且支持正则,使用方式如下:
Baige
2022/03/22
9290
Github-ThinkPHP 2.X 任意代码执行
开发中经常用到的代码
http://surl.sinaapp.com/ 引入百度CDN公共库 地址:http://t.cn/zYDC8wj
老高的技术博客
2022/12/27
4710
ThinkPHP-PHP开发中的主流框架
PHP 中的框架是什么? 框架就是通过提供一个开发 Web 程序的基本架构,PHP 开发框架把 PHPWeb 程序开发摆到了流水线上。换句话说,PHP 开发框架有助于促进快速软件开发(RAD),这节约了开发时间,有助于创建更为稳定的程序,并减少开发者的重复编写代码的劳动。 框架专门用于为这些常用元素(数据库交互、表示层、应用程序逻辑)提供结构,以便可以花费更少的时间来编写数据库接口代码或者表示层接口,而花费更多的时间来编写应用程序本身。以这种方式分解应用程序,这种架构被称为模型-视图-控制器(Mod
沈唁
2018/05/24
2.9K0
PHP-ThinkPHP框架学习
每个项目都有一个独立的配置文件(位于项目目录的Conf/config.php),配置文件的定义格式均采用PHP返回数组的方式,例如:
偏有宸机
2020/11/04
7K0
thinkjs学习笔记
IMWeb前端团队
2017/12/29
1.2K0
thinkPHP3.0框架实现模板保存到数据库的方法
本文实例讲述了thinkPHP3.0框架实现模板保存到数据库的方法。分享给大家供大家参考,具体如下: 在开发cms的时候用到如果将模板文件存入到数据库并显示到页面中 由于thinkphp3.0都是直接从模板文件中读取再解析的那么对于模板存入数据库中就只有自己开发了,还有thinkphp3.0中有mode的功能我们可以定义自己的mode这样就可以达到目的了,那么如何来扩展自己的mode呢?如下: 1.在你的入口文件中输入
用户2323866
2021/07/01
7430
骑士CMS模版注入+文件包含getshell复现
骑士cms人才系统,是一项基于PHP+MYSQL为核心开发的一套免费 + 开源专业人才网站系统。软件具执行效率高、模板自由切换、后台管理功能方便等诸多优秀特点。
Timeline Sec
2020/12/16
1.6K0
ThinkPHP框架运行流程
$GLOBALS['_beginTime'] = microtime(TRUE);
PM吃瓜
2019/08/13
1.2K0
CI基础知识笔记
1.知识点 $config[‘url_suffix’] = ”;//url后缀 $config[‘enable_query_strings’] = FALSE; $config[‘controller_trigger’] = ‘c’;//类似Thinkphp当中的m,可以传递c=news控制器名 $config[‘function_trigger’] = ‘m’;//类似Thinkphp当中的a,可以传递a=index方法名 $config[‘directory_trigger’] = ‘d’;
苦咖啡
2018/05/08
1.4K0
thinkphp框架解析0 -- 起源
THINKPHP是国人开发的PHP框架,如今终于深入内部打探了一番,下面给大家分享老高的心得。
老高的技术博客
2022/12/27
1.3K0
Yaf---Session | 模板 | 模型 | Cli模式
Yaf_Session是Yaf对Session的包装, 实现了Iterator, ArrayAccess, Countable接口, 方便使用.关于Yaf_Session的文档介绍:http://www.laruence.com/manual/yaf.class.session.html
Marco爱吃红烧肉
2021/07/23
2K0
Phalcon VS Spring 用法对照手册
Phalcon VS Spring 摘要 Phalcon VS Spring 用法对照表 ---- 目录 1. Install 1.1. Phalcon 1.2. Spring 2. Project initialization 2.1. Phalcon 2.2. Spring 3. Controller 3.4.1. Phalcon 3.4.2. Spring 3.3.1. Phalcon 3.3.2. Spring 3.2.1. Phalcon 3.2.2. Spring 3.1.1. Phalcon
netkiller old
2018/03/05
1.1K0
Thinkphp的Common模块作用
Common模块和普通模块一样,可以添加控制器、模型和视图,并且支持多层,但不能直接访问,只能继承,其中模型层 可以作为公用模型,在D方法实例化中调用。D函数实例化的时候 才能自动判断 当前模块模型类不存在的时候实例化公共模块中的同名模型,自己实例化肯定做不到这点了。
PM吃瓜
2019/08/12
2.7K0
Thinkphp的Common模块作用
带你走进PHP session反序列化漏洞
前些天打了巅峰极客,遇到了一题 session 反序列化,借此机会整理一下php session 反序列化的前生今世,愿与君共勉,如若有错,还望斧正
p4nda
2023/01/03
1.8K0
Thinkphp框架的项目规划总结和踩坑经验
1、项目分为PC端、移动端、和PC管理端,分为对应目录为 /Application/Home,/Application/Mobile,/Application/Admin;
PM吃瓜
2019/08/12
2.5K0
Thinkphp框架的项目规划总结和踩坑经验
thinkphp3.2.3代码审计
源码地址:http://www.thinkphp.cn/download/610.html
F12sec
2022/12/30
1.2K0
thinkphp3.2.3代码审计
thinkphp常用配置config
下载解压 ThinkPHP 3.2.3,在默认的应用 Application(./Application) 中,包含一个默认的模块 Home(./Application/Home)。
PM吃瓜
2019/08/12
3.1K0
thinkphp常用配置config
骑士 CMS 远程代码执行分析
续师傅前些天跟我说骑士 CMS 更新了一个补丁,assign_resume_tpl 这个全局函数出现了问题,让我分析看看能不能利用,我看了下官网公告:
p4nda
2023/01/03
1.1K0
骑士 CMS 远程代码执行分析
使用easyswoole进行开发web网站
easyswoole作为swoole入门最简单的框架,其框架的定义就是适合大众php,更好的利用swoole扩展进行开发,
仙士可
2019/12/19
1.7K0
使用easyswoole进行开发web网站
相关推荐
访问网站首页 index.php,跟着执行流程走一遍
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验