本文是 代码“中间地带”的封装与复用 的后续。
咱不谈设计模式这种“高档”货,也不谈M××,只谈怎么消除Copy+Paste,消除拖窗体设置属性这类耗时、易错的动作。
前些天和我徒弟聊天,他沾沾自喜的说他一天要写500行css,俺打击他说,啥时候他一天写到100行时才算悟了。
写代码就是一个由多到少的过程:
(1)见山是山时,一天能写1000行,大量的Copy+Paste,这叫代码迷宫
(2)见山不是山时,一天能写500行,Copy+Paste变成了大量的设计模式,这叫类的迷宫,Java程序尤其擅长这个,搞得我现在不敢看Java程序了。开个玩笑,重构到模式,实际上是从一种垃圾(代码迷宫)重构到另一种垃圾(类的迷宫)
(3)见山还是山时,一天能写100行,回归本源
先描述一个很常见的 Copy+Paste 场景:为客户端程序添加NotifyIcon,当关闭窗体时,窗体消失,缩小到托盘,双击托盘图标,则窗体又重现。
对于这一需求,msdn 的样例如下:
Code
using System;
using System.Drawing;
using System.Windows.Forms;
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.NotifyIcon notifyIcon1;
private System.Windows.Forms.ContextMenu contextMenu1;
private System.Windows.Forms.MenuItem menuItem1;
private System.ComponentModel.IContainer components;
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
public Form1()
{
this.components = new System.ComponentModel.Container();
this.contextMenu1 = new System.Windows.Forms.ContextMenu();
this.menuItem1 = new System.Windows.Forms.MenuItem();
// Initialize contextMenu1
this.contextMenu1.MenuItems.AddRange(
new System.Windows.Forms.MenuItem[] {this.menuItem1});
// Initialize menuItem1
this.menuItem1.Index = 0;
this.menuItem1.Text = "E&xit";
this.menuItem1.Click += new System.EventHandler(this.menuItem1_Click);
// Set up how the form should be displayed.
this.ClientSize = new System.Drawing.Size(292, 266);
this.Text = "Notify Icon Example";
// Create the NotifyIcon.
this.notifyIcon1 = new System.Windows.Forms.NotifyIcon(this.components);
// The Icon property sets the icon that will appear
// in the systray for this application.
notifyIcon1.Icon = new Icon("appicon.ico");
// The ContextMenu property sets the menu that will
// appear when the systray icon is right clicked.
notifyIcon1.ContextMenu = this.contextMenu1;
// The Text property sets the text that will be displayed,
// in a tooltip, when the mouse hovers over the systray icon.
notifyIcon1.Text = "Form1 (NotifyIcon example)";
notifyIcon1.Visible = true;
// Handle the DoubleClick event to activate the form.
notifyIcon1.DoubleClick += new System.EventHandler(this.notifyIcon1_DoubleClick);
}
protected override void Dispose( bool disposing )
{
// Clean up any components being used.
if( disposing )
if (components != null)
components.Dispose();
base.Dispose( disposing );
}
private void notifyIcon1_DoubleClick(object Sender, EventArgs e)
{
// Show the form when the user double clicks on the notify icon.
// Set the WindowState to normal if the form is minimized.
if (this.WindowState == FormWindowState.Minimized)
this.WindowState = FormWindowState.Normal;
// Activate the form.
this.Activate();
}
private void menuItem1_Click(object Sender, EventArgs e) {
// Close the form, which closes the application.
this.Close();
}
}
于是,我们开发Client程序时,当需要这一功能,就把这样的代码复制过来,改吧改吧就完成了。
复制粘贴很恶心,一次两次还好,次数多了自己就烦了。并且这样一来,导致代码量膨胀——功能一多,一个Form的代码量就近千行,很不方便测试和维护。因此,有必要进行抽象和复用。
这种抽象,属于对类的局部行为进行修饰(设计模式放在括号里谈,不想看的可以忽略——它不同于Mediator,也不同于Wrapper,一时也想不出好的名字),就叫Helper了。单纯的Helper又体现不出具体的意图,俺就命名为FormConfig。下面是 FormConfig 的代码:
Code
public class FormConfig
{
public NotifyIcon NotifyIcon { get;private set; }
public ContextMenuStrip NotifyIconMenuStrip { get;private set; }
public Boolean EnableFormExit { get; set; }
private Form Form { get; set; }
/// <summary>
/// 为窗体添加 NotifyIcon。并为其配置默认的右键菜单。双击NotifyIcon,激活窗体。
/// </summary>
/// <param name="form">窗体</param>
public void AttachNotifyIcon(Form form)
{
AttachNotifyIcon(form, null, null, CreateNotifyIconMenuStrip());
}
/// <summary>
/// 为窗体添加 NotifyIcon。双击NotifyIcon,激活窗体。
/// </summary>
/// <param name="form">窗体</param>
/// <param name="title">NotifyIcon 的 Title,若为空,则使用窗体的 Text 作为 NotifyIcon 的 Title </param>
/// <param name="icon">NotifyIcon 的 图标,若为空,则使用窗体的 图标作为 NotifyIcon 的图标 </param>
/// <param name="notifyIconMenuStrip">NotifyIcon 的右键菜单</param>
public void AttachNotifyIcon(Form form, String title, Icon icon, ContextMenuStrip notifyIconMenuStrip)
{
if (form == null) throw new ArgumentNullException("form");
if (notifyIconMenuStrip == null) throw new ArgumentNullException("notifyIconMenuStrip");
if (NotifyIcon == null) NotifyIcon = new NotifyIcon();
if (NotifyIconMenuStrip == null) NotifyIconMenuStrip = CreateNotifyIconMenuStrip();
NotifyIcon.ContextMenuStrip = NotifyIconMenuStrip;
NotifyIcon.Text = title == null ? form.Text : title;
NotifyIcon.Icon = icon == null? form.Icon : icon;
NotifyIcon.DoubleClick += new EventHandler(NotifyIcon_DoubleClick);
Form = form;
Form.FormClosing += new FormClosingEventHandler(Form_FormClosing);
}
private void NotifyIcon_DoubleClick(object sender, EventArgs e)
{
ShowMainForm();
}
private ContextMenuStrip CreateNotifyIconMenuStrip()
{
ToolStripMenuItem itemShowForm = new ToolStripMenuItem("显示主窗口");
ToolStripMenuItem itemCloseForm = new ToolStripMenuItem("退出");
itemShowForm.Click += new EventHandler(ItemShowForm_Click);
itemCloseForm.Click += new EventHandler(ItemCloseForm_Click);
ContextMenuStrip cms = new ContextMenuStrip();
cms.Items.Add(itemShowForm);
cms.Items.Add(itemCloseForm);
return cms;
}
private void ItemShowForm_Click(object sender, EventArgs e)
{
ShowMainForm();
}
private void ItemCloseForm_Click(object sender, EventArgs e)
{
if(Form!=null)
{
EnableFormExit = true;
Form.Close();
NotifyIcon.Visible = false;
}
}
private void Form_FormClosing(object sender, FormClosingEventArgs e)
{
if (!EnableFormExit)
{
e.Cancel = true; // 取消关闭窗体
Form.Hide();
Form.ShowInTaskbar = false;
NotifyIcon.Visible = true;//显示托盘图标
}
}
private void ShowMainForm()
{
if (Form == null) return;
Form.Show();
if (Form.WindowState == FormWindowState.Minimized)
Form.WindowState = FormWindowState.Normal;
Form.Activate();
}
}
使用起来很简单:
Code
public partial class MainForm : Form
{
private FormConfig FormConfig { get; set; }
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
FormConfig = new FormConfig();
FormConfig.AttachNotifyIcon(this);
}
}
其实这个还可以简化,因为毕竟还要声明一个FormConfig,再new一个,再Attach一下子,3行代码。进一步偷懒的话可以设计一个扩展方法,然后把FormConfig实例放在一个公共对象中进行管理,当Form退出时,再在公共对象中置空FormConfig。就不写代码了,简化到目前这个地步,俺已经满足了。
== 附外3篇 ==
下面这两篇和上面的这篇都是昨天晚上写的。第一篇是对这一段时间代码整理的总结,有点私人化。第二篇是讲通用验证码识别的一个思路,不是完整的文章,同时……又涉及点技术秘密,没办法完全透露。再附一篇完全搞笑的。
附1:代码整理的总结
搞了四个月的工作室,失败了,散了,好在成本严格控制住了,也没亏多少,最大的收获就是得到了证实——此路不通。
还是坚持当IT宅男,坚持向产品和高端转型,增强竞争优势。我希望投资回报是一条上升的线,而不是一条下降的线。
于是最近做的事情主要是整理代码和复习和学习数学。今后将玩数学了,主要做数学密集的应用。(看现在经济形势,10年内会新出现7000万白领,会减少7000万蓝领,蓝领的平均工资将于白领持平。国内又提供不了这么多白领岗位,IT界的竞争将无比的残酷。作为没有组织做后盾的IT宅男,俺必须早点布局。好在现在大部分大学生大学都是白混的,是数学白痴,和以前的俺一样)。
一、应用结构
1、 采用C-S结构,关键部分的计算放在S端,方便升级、收费。今后将引入P2P。
2、 自己用的话,用WPF开发界面,客户用的,用Winform开发。核心代码尽量与UI无关。
3、 将不再承接Web开发为主的项目,不承接数据库开发为主的项目
二、项目结构
1、微观结构
bin
lib
release
src
2、宏观结构
(1)Orc基础库主要封装一些常用的类、扩展方法、Mediator,以及一些基础设施,顺带储存自己用的snippets
嘿嘿,俺自定义的snippet只有一个:
Code
public event EventHandler<$YourEventArgs$> $YourEventHandler$;
protected virtual void On$YourEventHandler$($YourEventArgs$ e)
{
EventHandler<$YourEventArgs$> handler = $YourEventHandler$;
if (handler != null)
{
handler(this, e);
}
}
$end$
(2)OrcSmart库计划包括
(a)Orc.SmartImage,对常用的图像处理、分析、识别算法进行封装,基于AForge.Net和OpenCV进行开发,并新添一些这两个库尚未实现的算法。目前基本成型。
(b)Orc.SmartText,对常用的文本分析,NLP算法进行封装,这个库现在还是一个空架子,还未行动。
Orc.SmartCore被我归入Orc基础库里面,这个主要实现常用统计计算和机器学习算法。
(3)Orc协议库对常用协议进行实现与封装,目前实现了QQ2005-2007的协议,YMsg协议,封装了MSNP协议。今后将主要针对基于http的Web应用进行封装。
(4)OrcEntity库,封装常用实体类。
(5)Forsaken库主要针对3D应用,基于XNA,目前还是空架子,还未行动(一没时间,二外部环境还不成熟)。嘿嘿,主要目标是3D仿真。
三、第三方库选择:
(1) 使用nunit 进行单元测试(nunit比vs自带的单元测试好用很多)
(2) 使用log4net 记录日志
(3) 使用protobuf 进行序列化
(4) 图像处理库:AForge.Net,OpenCV(Emgu CV)
(5) 数学库:MathNet.Iridium
附2:通用验证码识别的一个思路
要实现通用的验证码程序,我认为应该具备以下2点:
(1) 不需要切割
(2) 不需要分离背景和前景
下面是我的思路:
(1) 边缘检测获得边缘
(2) 计算样本的ShapeContext,因为要考虑到干扰线,ShapeContext没法归一化
(3) 计算测试图片的ShapeContext(也可以是其它的描述子,但我感觉对于验证码来说,ShapeContext更好用一些,抗干扰能力更强一些。也可以计算不同Scale下的ShapeContext,来解决样本和测试图片中字符大小不一致的问题)
(4) 将测试图片的ShapeContext存入多维索引中,便于查询
(5) 对于样本,设定一个匹配的阈值,为每个点寻找测试图片中的匹配点(每个寻找2-3个匹配点),这样,所有的匹配点会在2D空间内呈现一种分布,这种分布会有0个或数个聚集区,每一个聚集区就是一个候选区,通过聚类算法寻找聚集区
(6) 假定图片中有一个字符W,这个字符既可以匹配W,又可以匹配V,甚至能匹配I,L这两个字符,因此应该设计一个筛选算法
附3:江湖