团队切换器

在/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. Tomcat jdbc pool配置

    Tomcat jdbc pool是apache在tomcat7版本中启用的新连接池,用它来解决以往DBCP无法解决的一些问题. Tomcat jdbc pool的优点: (1)    tomcat j ...

  2. Chrome调试大全--转载

    作为一名前端开发者,打交道最多的可能是和浏览器.市面上各种浏览器多不胜数,主流的有Chrome,Firefox,Safari,IE,Opera,非主流的如360,遨游,QQ浏览器,搜狗浏览器,据说淘宝 ...

  3. [转] 看懂UML类图和时序图

    PS: 组合关系:实心,一个类A属于另一个类,或多个类,但是类A不能单独存在去使用,A一般是一种抽象的东西 聚合关系:空心,一个类A可以单独存在使用 不论组合聚合,A的方法都会被直接调用. 看懂UML ...

  4. [转] shell文本字符串处理

    第一种方法:#%*,#即截取变量前的字符(左向右截取),%表示截取后面字符(右向左截取),*匹配符 var=foodforthought.jpg ${varible##*string} 从左向右截取最 ...

  5. 《UNIX网络编程》之read_timeout实验

    最近在做项目,需要做一个服务器和客户端的基于TCP的套接口网络编程,由于服务器端返回数据并不是那么的及时,因此,需要在客户端做些延迟,然后才能去读取数据,实验测试结果如下. 首先,我们先来看一下我们封 ...

  6. Android自定义属性、控件三步法

    第二步中layout-activity_main.xml 中自命名控件: xmlns:android="http://schemas.android.com/apk/res/android& ...

  7. Sorting File Contents and Output with sort

     Sorting File Contents and Output with sort   Another very useful command to use on text file is  so ...

  8. 通过URL推送POST数据

    由于到了一家新公司重新开始接触MVC和其他的一些东西.所以的重新拾起许多东西. 前一段时间让我写一个和第三方公司推送对接的方法.通过对方提供的URL把数据post推送出去. 我把url到了web.co ...

  9. (转)background-position—CSS设置背景图片的位置

    background-position :在 CSS 中通过 background-position 属性可以调整背景图片的位置.因为在默认情况下背景图片都是从设置了 background-posit ...

  10. css布局之负margin妙用及其他实现

    相信大家在项目的开发中都遇到过这样的需求,一行放X(X>1)个块且相邻块之间的间距相同. 大概就是上面这个样子,下面介绍几种实现的方式. 1.负margin大法 设置好元素的宽度和留白占满父级的 ...