您的位置:首页 > 编程语言

敏捷开发“松结对编程”系列之十六:L型代码结构(编程篇之二)(上)

2013-02-02 13:45 447 查看
本文是“松结对编程”系列的第十六篇。(松结对编程栏目目录
今天正好要复用一段框架(asp.net MVC3,服用范围包括Controller和View),把过程记录一下。
与复用一般的过程相比,L型代码结构有这么几个特点:
1. 如果复用有难度,在复用之前,一般不刻意形成“可复用代码”。顺便就能写成函数的例外。
2. 从第二次复用的时候,就形成复用代码。
这个听起来很容易,但要求:编写复用代码的人,就是调用复用代码的人,否则他不知道是否会复用,以及复用到第几次了。换言之,不要安排人刻意编写复用代码,否则很容易还没人用,或只用一次,就费劲写了一个可复用代码;又很容易大家一直在重复的东西,他却不知道。如果能一边编写上层应用知道使用的实际情况,一边编写底层可复用库,就能避免这种情况。

业务需求

现在需要编写一个对产品线下的产品进行“增删改查”操作的业务。想象中的界面如下(实际是完成后的截图):


想到之前曾经编写过一个为部门下的团队进行“增删改查”操作的业务,界面大致如下:


决定借用这个页面,顺带把代码也全部借用了(除了里边不显示人员列表外,几乎没有区别),而且这两个东西的基类还是相同的(这一点很重要),分别是
Product : Item(父子关系项) : UDCable(可自定义字段的)
Department: Item(父子关系项) : UDCable(可自定义字段的)
注意下面代码中的Program(部门)和Team(团队,隶属于部门)基类都是Department。
原来Teams的处理代码如下:

public ActionResult Index(int? programID)
        {
            return MFCDefaultView(programID ?? _repMFC.ReadAllItems<Program>().First(i => i.Type == ItemWhattype.DepartmentTypeProgram).ID);
        }

public ActionResult Create(int programID, string redirectToAfterCreated, string returnUrl = null)
        {
            return Redirect("/MFC/Items/Create?fatherID=" + programID
                            + "&what=" + SystemItemWhat.Deaprtment + "&type=" + ItemWhattype.DepartmentTypeTeam + "&returnUrl=" +
                            HttpUtility.UrlEncode(returnUrl));
        }
    ...
    }

删除、左右移动、编辑的代码不在这个里边,是由ItemsController也就是他们的基类Item的一个公共Controller处理的。本来Index/Create/Edit/Details在那边都有公共的代码和界面,但是因为Team的页面比较特殊,比如每次创建后不是返回Index页面,而是前往成员分配页面,所以都在这边重新做了函数和页面,不过其中一些指向ItemsController中的函数,比如上面这个Create()。
新业务需求很类似这个,所以与其重新写一个,不如重构到一起,日后再用这种东西,从代码到界面,就可以直接使用了。

重构顺序

这种因复用导致的重构有很多工作顺序,大致有两个:
1. 先写那个ProductsController,然后比较与TeamsController的区别,然后提取共同部分复用,然后把Teams和Products两个都基于复用实现
坏处是要写两边,而且写出来的ProductsController以及相应的几个View很快就要被扔了。
这个适合初学者通过对比发现可复用代码的抽取,就是效率比较低,仅作学习使用。
2. 直接写一个可复用底层,基于此底层写ProductsController,调通之后把TeamsController也按此底层实现。
坏处是Products本身的Bug可能和复用底层的Bug纠缠在一起,调试麻烦。
3. 先根据Teams写一个底层,然后先把Teams写成基于可复用代码的,调试通过了再直接基于底层写Products的。
坏处是可能只看着Teams未必能写出可复用底层,需要服用的经验比较多。
好处是调试过程比较简单,可以把TeamsController当作一个测试用例,来测试那个可复用底层。
本人这次选择方法3(倒不是因为它最好,之前代码不多的时候选择过1,比较有信心的时候选择过2)。

重构步骤

步骤中只处理Index函数(Create函数见下一篇文章)。

1. 先在ItemsController里边写个替代Teams/Index的Action

这个新函数就叫做ItemHorizentalList吧(横向列表)

public ActionResult ItemHorizentalList(int rootID, string what, string type, string returnUrl)
        {
            var root = _repMFC.ReadAt<Item>(rootID);
            IEnumerable<Item> subItems = root.SubItems().Where(i => i.What == what && i.Type == type);
            return MFCDefaultView(root, subItems, what, type, returnUrl);
        }

rootID表明是哪个部门Program下面的团队Team,或哪个产品线ProductLine下的产品Product,whats和whattypes是确定显示什么类型的item的,一个item到底是部门Program/团队Team/产品线ProductLine/产品Product,就由他们说了算。
后面这个 return MFCDefaultView请暂时无视,它的工作是同时向View中传输多个参数。以往asp.net的Controller.View()只能传一个参数,要传多个就要使用ViewBag,或者ViewModel(尽管“ViewModel”是一种设计模式,但是不但还要额外写个viewModel,还要经常到里边看看里边的代码,很破坏代码可读性)。这个MFCDefaultView可以传入可变参数列表,在PageData[]中顺序读取就可以了,也就是说调要一个view的过程很像调用一个函数。这个也是我们最近刚刚封装的,从此向iewBag和ViewModel说再见了。

2. 在这个新Actoin的View里边实现原来Teams/Index的工作

原来的View代码(views/teams/index.cshtml):

this.InitializeLayout("团队");

    <h2>所有团队</h2>
    <hr/><br/>
    <div class = "item-hierarchy @MFCUI.HoverTwinkleTriggerBodyClass("create-new-team")" style = "float: left; min-height: 10px; min-width: 10px; margin-right: 16px; ">
        <div class = "item-hierarchy-body">
            @MFCUI.ImageLink("新建团队", "/Site/Teams/Create?departmentID=" + Model.ID + "&redirectToAfterCreated=" 
                               + HttpUtility.UrlEncode("/Site/TeamMembers/Index?departmentID=" + Model.ID), 
                       imgUrl: "/Site/Teams/Create48.png", showText: false)
        </div>
    </div>
    @RenderPage("Index/_Teams.cshtml", Model.SubItems().Where(i => i is Team).Cast<Team>(), false)
上面的<div>显示文章开头图中的那个新建按钮,下面的RenderPage显示所有右边的已有团队。
里边到底有哪些需要参数化的呢?凡是带着Department, Team, /Site/Teams/...的,都要参数化。具体可以看下面的结果。

新的View(Views/Items/ItemHorizentalList.cshtml):

var root = PageData[0] as Item;
    var subItems = PageData[1] as IEnumerable<Item>;

    <div class = "item-hierarchy @MFCUI.HoverTwinkleTriggerBodyClass("create-new-team")" style = "float: left; margin-right: 16px; min-height: 10px; min-width: 10px;">
        <div class = "item-hierarchy-body">
            @MFCUI.ImageLink("新建", "/MFC/Items/Create?rootID=" + root.ID + "&redirectToAfterCreated=" + HttpUtility.UrlEncode(redirectAfterCreate),
                           imgUrl: "/MFC/Items/Create48.png", showText: false)
        </div>
    </div>
    @RenderPage("ItemHorizentalList/_SubItems.cshtml", subItems, false)

前面一堆var把1里边提到的那个MFCDefaultView的参数拆包;中间Div和后面的RenderPage,根据参数修改。
具体需要哪些参数,取决于怎样把原来针对Team的操作变成通用的Item的操作。

3. 调整新的view所调用的partial view

然后一点点调整,逐渐把_SubTeams.cshtml变成_SubItems.cshtml,_Team.cshtml变成_SubItem.cshtml。
由于product和team都是从item派生的,所以当作item来操作就可以了。
调一下就刷新一下页面,看看效果。逐渐地,界面正常了:



4. 用这个框架实现Product的页面:

C#代码就一行,在ProductsController.cs里边:
public ActionResult Index(int? productLineID)
        {
            return MFCDefaultView(productLineID ?? _repMFC.ReadAllItems<ProductLine>().First(i => i.Type == ItemWhattype.ProductTypeProductLine).ID);
        }

View只有一个,就是体现Team和Product中间显示不同的部分,下一篇再写。

残留问题

但最后会遇到几个最关键的问题:
1. team要显示成员,product要显示别的(暂定为最近的发布Release)。
Index实际上不知道这件事情,因为它只收到了Item,这个基类中并没有成员或发布Release的信息。
2. 新建好team要转向成员分配(/TeamMembers/Index),新建好product要转向创建发布(/Products/ReleaseSchedule)。
3. 哪些Action需要写View,然后在View里边RenderAction(比如这个Teams/Index);哪些不用写View,直接在Controller的Action里边RedirectToAction到ItemsController的操作?(这样简单,连View都不用谢,但有局限就是Url会跳转)?
4. 这些编码方法与松结对编程有何关系?
这两个问题的解决,下一篇再写。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐