团队切换器

在/Teams/Details?id=xxx的页面,有这样一个控件,使得不需要回到/Teams/Index就能轻松切换团队:

由于这种团队切换控件比比皆是,比如在团队故事板中(以及其他地方若干处):

所以希望开发一个控件。

这个控件应该能:

1. 里边所包含的链接自动跟随页面的链接。

比如第一张图里边,指向/Teams/Details?id=XXX;而第二张图里边,则是指向/Agile/TeamStoryBoards/Details?teamID=xxx

2. 下拉菜单里边的所有链接,会因为id或teamID不同而指向指定团队的页面。

设计思路

调用代码:
        <div class = "link-span spliter">
切换团队:@Team.TeamsDropdownList(this, "id")
</div>
调用代码总是很像自然语言,他说:在这个页面(this)的链接里有个字段("id")有差异,随Team而变化。在调用代码中,任何比自然语言多的内容基本上都是垃圾内容。

实际上可以看出,Url本身不能硬编码写进去,而是应该从页面自动获取;获取后,把不同链接中的id=xxx换成不同Team的ID。

实际代码:
    public partial class Team : Department
{
public static HelperResult TeamsDropdownList(WebViewPage page, string urlKey) //urlKey就是"id"或"teamID"这两个,用来表示需要替换URL中的哪个参数的
{
var currentTeamID = page.ParameterOf(urlKey); //提取id或teamID的实际值,里边利用了page.Request.RawUrl,也可以用page.Request.Queries(我们以前不知道有这个)。
var program = Program.Default(); //取得缺省部门,部门是团队Team的上一个级别。
var currentTeam = string.IsNullOrEmpty(currentTeamID) ? null : program.Teams().Single(i => i.ID.ToString() == currentTeamID); //下拉菜单中的当前团队。
return MFCUI.DropdownListHtml(page,
currentTeam == null ? new MvcHtmlString("选择团队") : MFCUI.ImageLink(currentTeam.Title, page.Request.RawUrl, displayAsBoldTextOnPage: page),
program.Teams().Select(i => MFCUI.ImageLink(i.Title, page.MergeParameter(urlKey, i.ID.ToString()),
displayAsText: currentTeam != null && i.ID == currentTeam.ID)));
}
MFCUI.DropdownListHtml是另外一个产生下拉菜单的控件,不多说了,关键是其中的一句话:
page.MergeParameter(urlKey, i.ID.ToString())

这句话把page.Request.RawUrl中的"id"或"teamID"替换为program.Teams()中不同团队的ID,生成其新链接。
这样,这个方法就能在任何页面,利用一个id/teamID等区分字段名来自动产生团队切换的效果,而链接到底指向哪里不关心。

扩展

之前说的这个东西不错,可是在另外一种场景中不能直接用,比如下面这个“切换产品”:

调用代码长得很像:
    <div class="link-span spliter">
切换产品:@Product.ProductsDropdownList(this, "id")
</div>
拷贝代码改写的结果是给Product增加一个类似函数:
        public static HelperResult ProductsDropdownList(WebViewPage page, string urlKey)
{
var currentTeamID = page.ParameterOf(urlKey);
var productLine = ProductLine.Default();
var currentProduct = string.IsNullOrEmpty(currentTeamID) ? null : productLine.Products().Single(i => i.ID.ToString() == currentTeamID);
return MFCUI.DropdownListHtml(page,
currentProduct == null ? new MvcHtmlString("选择团队") : MFCUI.ImageLink(currentProduct.Title, page.Request.RawUrl, displayAsBoldTextOnPage: page),
productLine.Products().Select(i => MFCUI.ImageLink(i.Title, page.MergeParameter(urlKey, i.ID.ToString()),
displayAsText: currentProduct != null && i.ID == currentProduct.ID)));
}

这两个方法的内容几乎完全相同,这是代码坏味道的先兆;比如以后修改了MFCUI.DropdownListHtml,就要回来修改两个地方。

幸亏Product和Team都继承自基类Item(用于处理树状结构,如Father,Brothers,SubItems之类的),这样就可以用基类进行改写:
不过,改写的时候最好遵循某些步骤,否则很容易在半路上撂挑子。

步骤1:改写其中一个函数,去掉具体的类型

我先拿Product里边那个动刀,在原来函数的下面(不要直接给改了),增加一个新的函数:
        public static HelperResult BrothersDropdownList(WebViewPage page, string defaultText, Item father, string whattype, string urlKey)
{
var currentId = page.ParameterOf(urlKey);
var currentItem = string.IsNullOrEmpty(currentId) ? null : father.SubItems().Single(i => i.ID.ToString() == currentId);
return MFCUI.DropdownListHtml(page,
currentItem == null
? new MvcHtmlString(defaultText)
: MFCUI.ImageLink(currentItem.Title, page.Request.RawUrl, displayAsBoldTextOnPage: page),
father.SubItems().Where(i => i.WhatType == whattype).Select(i => MFCUI.ImageLink(i.Title, page.MergeParameter(urlKey, i.ID.ToString()),
displayAsText: currentItem != null && i.ID == currentItem.ID)));
}
所有不同点,都被提取成一个参数:

defaultText用来处理“选择团队”和“选择产品”;father用来处理Program.Default()和ProductLine().Default();

whattype是我们用于在 Item基类层面上区分不同的类型的,比如产品有产品线(ProductLine)-产品(Product)-版本-发布等层级;而团队有公司-子公司-部门(Program)-团队(Team)等层级。

father.SubItems().Where(i => i.WhatType == whattype)

就是问father的下一级别中的whattype类型的。这样如果传入father=ProductLine.Default()和whattype=ItemWhatType.ProductProduct,就能得到所有产品,和原来代码结果相同。

现在, 这个方法虽然还在Products下,但里边已经和Product完全无关了,这是步骤1的目标
在网页上测试一下:
    <div class="link-span spliter">
切换产品:@Product.ProductsDropdownList(this, "id")
切换产品:@Product.BrothersDropdownList(this, "切换产品", ProductLine.Default(), ItemWhattype.ProductProduct, "id")
</div>

最好的测试方法,就是像上面这样做“对比测试”,然后看两者有何不同;这个很像敏捷开发里边提到重构时,要验证产品的外部功能没有发生变化一样。

步骤2:挪到基类中

基类叫Item,函数名不变还是BrothersDropdownList,代码不写了。
重新做对比测试:
    <div class="link-span spliter">
切换产品:@Product.ProductsDropdownList(this, "id")
切换产品:@Product.BrothersDropdownList(this, "切换产品", ProductLine.Default(), ItemWhattype.ProductProduct, "id")
切换产品:@Item.BrothersDropdownList(this, "切换产品", ProductLine.Default(), ItemWhattype.ProductProduct, "id")
</div>


结果是三个控件功能完全相同,好了,删除前两个;再去Team那边把Team的方法删除,调用也做相应修改。

以后任何Item派生类都能在桌面上扔这样一个控件来切换了。
在火星人里边所有东西都是Item,所以复用的次数非常多。

总结

1. 任何相似代码都是坏味道
2. 调用代码的信息量应该与自然语言相同
3. 重构时做对比测试。
一些其他前提:凡是用两次的东西都可能值得做个积累。
这个“积累”或许是个方法,或许是个基类。比如上面提到的MFCUI.DropdownList()/this.ParameterOf()/this.MergeParamegter()这些,都是以前为了别的事情做好了的。
如果没有积累,每次想复用的时候就会发现要做很多准备工作,会情不自禁地想“算了这次先这样吧”。时间长了破罐子破摔,代码就越来越烂了。

简单干净的C# MVC设计案例:BrothersDropdownList()的更多相关文章

  1. 从YouTube改版看“移动优先”——8个移动优先网站设计案例赏析

    2011年,Luke Wroblewski大神提出了移动优先的设计理念.在当时看来这无疑是一个打破行业常规的新型设计原则.而在移动互联网大行其道的今天,谁遵守移动优先的设计理念,设计出最好的移动端网站 ...

  2. MVC 小案例 -- 信息管理

    前几次更新博客都是每次周日晚上到周一,这次是周一晚上开始写,肯定也是有原因的!那就是我的 Tomact 忽然报错,无法启动,错误信息如下!同时我的 win10 也崩了,重启之后连 WIFI 的标志也不 ...

  3. 讨论Android开发中的MVC设计思想

    最近闲着没事,总是想想做点什么.在时间空余之时给大家说说MVC设计思想在Android开发中的运用吧! MVC设计思想在Android开发中一直都是一套比较好的设计思想.很多APP的设计都是使用这套方 ...

  4. Spring MVC 设计概述

      MVC设计的根本原因在于解耦各个模块 Spring MVC的架构 对于持久层而言,随着软件发展,迁移数据库的可能性很小,所以在大部分情况下都用不到Hibernate的HQL来满足移植数据库的要求. ...

  5. 企业项目实战 .Net Core + Vue/Angular 分库分表日志系统二 | 简单的分库分表设计

    教程预览 01 | 前言 02 | 简单的分库分表设计 03 | 控制反转搭配简单业务 04 | 强化设计方案 05 | 完善业务自动创建数据库 06 | 最终篇-通过AOP自动连接数据库-完成日志业 ...

  6. TERSUS无代码开发(笔记05)-简单实例电脑端页面设计

    案例笔记电脑端页面设计   1.新建项目(请假管理qjgl)   2.开发软件界面介绍(常用的功能按键)      3.目录中显示元件对象      4.对元件对象的操作主要方式是双击(双击哪个元件, ...

  7. DDD领域驱动设计-案例建模设计-Ⅲ

    1. 背景 参考<DDD领域驱动设计-案例需求文档>,本文将构建实体,聚合根详述领域驱动中的建模设计.构建实体,聚合根的一些原则或方法,将在后续文章中说明. 2. 建模设计 2.1. 实体 ...

  8. Httpster –世界各地最潮的网页设计案例聚合网站

    Httpster 这个网站聚合了世界各地最新最潮的网页设计案例,展示了创意的设计,精心的策划,优秀的排版.这些作品都按月份和类别进行了很好划分,你可以方便的找到自己感兴趣的网站案例. 立即去看看 您可 ...

  9. Linux下一个简单的日志系统的设计及其C代码实现

    1.概述 在大型软件系统中,为了监测软件运行状况及排查软件故障,一般都会要求软件程序在运行的过程中产生日志文件.在日志文件中存放程序流程中的一些重要信息, 包括:变量名称及其值.消息结构定义.函数返回 ...

随机推荐

  1. GCC编译选项

    一.看例子分析gcc 的编译选项 gcc -o hello hello.c -I /home/hello/include -L /home/hello/lib -lworld 1.-I /home/h ...

  2. Lombok介绍及使用方法

    lombok简介 lombok是暑假来到公司实习的时候发现的一个非常好用的小工具,刚见到的时候就感觉非常惊艳,有一种相见恨晚的感觉,用了一段时间之后感觉的确挺不错,所以特此来推荐一下. lombok的 ...

  3. MS SQL Sever数据库还原

    一.右键 数据库 二.点击 [还原文件和文件组(E)...],弹出下图的窗口界面 1.在 目标数据库 的输入框填写你的数据库名(注意这是新建一个数据库供还原使用,不能还原到已有的数据库) 三.点击[源 ...

  4. android 屏幕适配1 ——dimens.xml的适配

    1.如果是才开始做项目,已经有设计图:720*1280 1).默认values文件夹:1dp=1px values/dimens_x.xml: name: x1~x720   value:1px~72 ...

  5. hadoop2.4.1伪分布式搭建

    1.准备Linux环境 1.0点击VMware快捷方式,右键打开文件所在位置 -> 双击vmnetcfg.exe -> VMnet1 host-only ->修改subnet ip ...

  6. UIVIew之霓虹灯实现

    // // AppDelegate.m // NiHongPractice // #import "AppDelegate.h" #define kColorValue arc4r ...

  7. JavaScript 客户端JavaScript之Document对象中的表单和表单元素

    Form对象 代表一个HTML表单(document可以有多个表单元素) 表单访问 document.form[document.forms.length-1] 访问表单元素 document.for ...

  8. Java前端Rsa公钥加密,后端Rsa私钥解密(目前还不支持中文加密解密,其他都行)

    Base64工具类,可以让rsa编码的乱码变成一串字符序列 package com.utils; import java.io.ByteArrayInputStream; import java.io ...

  9. Intellij idea 12和设置快捷键修改(加快项目的开发速度与养成良好习惯)

    1.为了养成良好的代码习惯idead中的javascript jSLint能显示不良的代码设置如下    2.Intellij idea 12每一次修改,保存生成都要按ctrl+shift+F9非常影 ...

  10. Thinkphp 空操作、空控制器、命名空间

    1.空操作 空操作是指系统在找不到请求的操作方法的时候,会定位到空操作(_empty)方法来执行,利用这个机制,我们可以实现错误页面和一些URL的优化. http://网址/index.php/Hom ...