(转载)提高系统OOP抽象以应对复杂的需求

提高系统OOP抽象以应对复杂的需求, 转自:http://www.nowamagic.net/librarys/veda/detail/1373
  有人问我如何构建一个比较好的类阶层次,如何使用面向对象进行设计,或者问为什么我看了那么多面向对象和设计模式的书一到使用的时候却总是写出面向过程的代码。每当我碰到这些问题的时候我总是回答,其实我也不知道。真的,其实我也不知道。
  虽然我总是张口闭口面向对象,总是看到一个问题后就谈这个有点XXX模式的影子,但大部分时候碰到一个问题我还是一片空白,不知道如何去分析设计和实现出好的面向对象。所以,我只想谈谈我是如何实践面向对象的,这对我自己有用但不一定对你有用。嗯,回到正题。
  回顾编程方法的发展史,我想不外乎两个字:抽象。
  从最早的汇编语言中使用的子例程到结构化编程,然后到面向对象、面向组件以及面向服务。我觉得都是不断地提升抽象的层次。所以编程方法没有好坏,只有适合不适合。在汇编时代问题规模都很小,所以我们需要的抽象能力不需要太强。而现代的软件项目,问题的规模非常庞大,需要考虑的事情非常多(虽然纯粹的技术含量不一定有汇编时代的高),我们就必须使用抽象层次更高的方法来匹配我们的问题规模。
  面向对象编程方法的出现也不外乎如此,所以我们在使用面向对象方法开发的时候一个目的就是要提升抽象层次(比如现在由有人提出面向对象已经不足以匹配并行软计算的抽象层次,所以不再教授面向对象,转而教授函数编程)。
  而我觉得提升抽象层次的一个好方法就是用代码与人交谈,用代码来表达你的思想,在代码中形成一个个"概念",或者说代码就是用来传递知识的。我将概念二字加上引号并加粗是有特别强调的意思,这个在后文我会谈谈什么是这里所说的概念。我不想在表面文字上谈论太多,我们来实践吧。
  注意,本文代码仅仅为了说明一些问题或现象,并不考虑业务上的合理性,读者可以自行分辨然后拿自己的业务代码进行思考。

方法的参数

  不知道你写过或见过下面的代码没有:
bool IsValid(string userName, string password, string email, int status);
  如果你见过然后还放任不管,那么你就丧失了一次提升抽象层次的机会。Robert.Martin在《Clean Code》里谈到,方法的参数不宜过多,如果有过多的参数我们就要特别审视一番。当我们审视上面的方法参数时,我们发现其实这些参数都应该属于同一个东西,而现在我们没有。类似字符串、整型这些类型,抽象层次太低了,没有任何的领域含义。而且我们发现,上面方法的参数和方法名字IsValid也不在同一个抽象的层次上,我们阅读到IsValid这个方法时,我们甚至不能一下子了解其目的:哦,这个方法是检查用户名、密码、邮件以及状态的有效性么?哎,多么啰嗦,还不一定对。如果我们多看两眼这些参数我们或许会写出这样的代码:
bool IsValid(User user);

public class User
{
    public string UserName{get;set;}

    public string Password{get;set;}

    public string Email{get;set;}

    //这里仅仅为了方便使用整型代替枚举,其实可以新建一个枚举来提升这里的抽象层次-_-
    public int Status{get;set;}
}
  哦,看到这个方法后我就知道这个方法是用来检查用户的合法性的,除此之外我们还创建了一个概念"用户",我们将一堆零散的数据聚合成一个新对象,向你传递了一个知识。

不要迷恋哥

  然后我们再进入到IsValid方法内部看看:
bool IsValid(User user)
{
    if(user.UserName.Length > 0 && user.Email.Contains("@")){
        //....
    }
    //...
}
  我们发现这个方法内部干的事儿就是不断的询问User对象,探寻User对象的内部状态然后做出一些判断。探寻别人的隐私是不好的,这么强的依赖别人的内部状态违反了面向对象封装的原则。如果我们的一段代码总是不断的探寻另外一个对象的内部状态,然后做出一些判断,我们就应该思索:这个被询问的对象是不是缺少一个概念?或者说这段代码应该属于被询问的那个对象而不是现在的这个对象:
public class User
{
    public bool IsValid()
    {
        if(userName.Length > 0 && email.Contains("@"))
        {
            //....
        }
        //...
    }
}

这里抱成一团

  有的时候我们发现,方法内部的某部分代码围绕着一个中心点在纠结。跟方法内的其他代码有些间隙,最重要的是这段代码严重的影响了整个方法的可读性,因为有了这段代码,方法体变长,方法更难读懂。这个时候我们应该对方法内部的代码排排序,检查一下这些代码,是不是有的代码是为了干一件事儿(方法刚写的时候可能是这样分的,为了同一个目的的代码都放到一块儿,但随着时间流逝,新代码不断的加入可能违反了这个原则)。比如上面的IsValid方法或许如下面这样实现:
public class User
{
    public bool IsValid()
    {
        if(userName.Length > 0 && (email.Contains("@") && (email.EndsWith(".com") || email.EndsWith(".biz")...)))
        {
            //....
        }
        //...
    }
}
  那一长串&&和||不就是为了验证Email的合法性么?因为它的存在搞得这里一团糟,如果我们能进一步提升抽象层次:将一团代码提取到一个方法,用方法名来描述方法本身:
public class User
{
    public bool IsValid()
    {
        if(userName.Length > 0 && IsEmailValid())
        {
            //....
        }
        //...
    }

    bool IsEmailValid()
    {
        return email.Contains("@") && (email.EndsWith(".com") || email.EndsWith(".biz")...));
    }
}
  不仅这团代码的抽象层次提高了,IsValid顿时也高贵起来了,好懂多了(不过那团代码依然存在,不过隐藏在抽象的背后,忘记是不是有这么一个名言:每个漂亮的接口后面都有一个肮脏的实现[玩笑话])。

人格分裂

  碰到过这样的代码没有,一个类里五个方法,三个方法访问a,b,c属性,另外两个方法访问d,e,f属性。好像有一条隐约可见的分界线将这个类一分为二。有的时候这条分界线并不十分明显,可能还有一些交叉。这实际上和上面说的提取一个方法类似。我们只是比方法更高一个层次:缺少一个类型将这部分代码独立出去。还是看上面的User类,我们发现有那么几个方法总是围绕着email在打转,对User类其他的东西倒不是很关心:
public class User
{
    public bool IsValid()
    {
        if(userName.Length > 0 && IsEmailValid())
        {
            //....
        }
        //...
    }

    bool IsEmailValid()
    {
        return email.Contains("@") && (email.EndsWith(".com") || email.EndsWith(".biz")...));
    }

    public string EmailAddress()
    {
        return string.Format("\"{0}\"<1>",userName,email);
    }

    private string Convert()
    {
        if(email.IndexOf(‘#‘) != -1)
        {
            return email.Replace(‘#‘,‘@‘);
        }
    }
}
我想或许我们缺少一个Email的概念,这样就可以将这几个方法以及其要使用的属性封装起来:
public class User
{
    private Email email;

    public bool IsValid()
    {
        if(userName.Length > 0 && email.IsEmailValid())
        {
            //....
        }
        //...
    }

}

public class Email
{
    private string address;

    bool IsEmailValid()
    {
        return address.Contains("@") && (address.EndsWith(".com") || address.EndsWith(".biz")...));
    }

    public string EmailAddress(string userName)
    {
        return string.Format("\"{0}\"<1>",userName,email);
    }

    private string Convert()
    {
        if(email.IndexOf(‘#‘) != -1)
        {
            return address.Replace(‘#‘,‘@‘);
        }
    }
}
  好了,就谈这么多吧。类似的提升抽象层次的例子还有很多很多,无非就是通过重组织代码,形成一些概念,向阅读代码的人传递领域的知识。然后我想说的是,面向对象设计或许真的很难,需要丰富的经验,但是面向对象编程并不难,只需要我们有一颗精益求精的心就可以了。代码不是写出来然后就放到那里,然后就不去管了,我们需要时不时的去照顾我们的代码,然后观察,然后不断的去雕刻。
  面向对象很像路边摊的大妈摊鸡蛋饼。不断的把饼摊薄,饼的每个小块都很薄,然后就很容易熟。

(转载)提高系统OOP抽象以应对复杂的需求

时间: 2024-11-18 11:20:29

(转载)提高系统OOP抽象以应对复杂的需求的相关文章

ASP.NET MVC3实战系列(二):面向接口编程,提高系统可测试性。

ASP.NET MVC 使用MVC的架构,其架构本身就使应用程序更易于测试,但这并不意味着可以随便写出易于测试的程序.我们都知道单元测试在系统开发有着很重要的作用. 我们来写这样的一个程序,系统获取某个坏男人的情人信息,然后发送给他老婆. 1. 建一个Lover的ASP.NET MVC3项目 我们需要1个实体类,存储男人,情人和老婆的信息. 然后我们需要一个LoverRepository来获取某个人的情人,这里就想成从数据库取数据.我们这里先返回固定的数据 建一个HomeController,

[转载]window系统下TCP参数优化

注:此文转载自红黑联盟,最近服务器遇到周期性down掉的问题,拖了两三周请教了前辈之后才知道无关应用和数据库的事情,是tcp设置的问题.从网上找个这篇文章,解释的很不错.有兴趣请至红黑联盟翻阅原文. TCP连接的状态与关闭方式及其对Server与Client的影响 http://www.2cto.com/net/201304/206071.html 通常会采用修改注册表的方式改进Windows的系统参数.下面将为大家介绍Windows系统下的TCP参数优化方式,适用于Windows 2003.W

[转载] 可伸缩系统的架构经验

原文: http://agiledon.github.io/blog/2013/02/27/scalability-system-architecture-lessons/ 最近,阅读了Will Larson的文章Introduction to Architecting System for Scale,感觉很有价值.作者分享了他在Yahoo!与Digg收获的设计可伸缩系统的架构经验.在我过往的架构经验中,由于主要参与开发企业软件系统,这种面向企业内部的软件系统通常不会有太大的负载量,太多的并发

关于某某系统增加相应功能,提高系统的可用性和易用性

通过阅读<大型网站技术架构:核心原理与案例分析>第五六七章,结合<xx系统>,分析如何增加相应的功能,提高系统的可用性与易用性的感想: 网站的可用性描述网站的可有效访问的特性(不同于另一个网站运营指标:Usability,通常也被译为可用性,但后者强调的是网站的可用性,即对最终用户的使用价值),相对于网站的其他非功能特性,网站的可用性更牵动人们的神经,大型网站不可用事故直接影响公司形象和利益,许多互联网公司都将网站可用性例如工程师的绩效考核,与奖金省钱等利益挂钩. 不同于其他架构指

【机房收费系统】抽象

是否为空?一开始我是用的isnull 来逐个判断的,但是重复的地方的确很多.我们秉着一个重复就要努力抽象的原则,同时争取相似的功能用不同的实现方式.今天算是做个小结,抛砖引玉,希望大家多多的提宝贵意见. 抽象,是为了实现更好的代码复用,也便于日后的维护.希望在做合作版的时候,能对这些问题有一个更加深刻的理解. 判断用户名是否存在 这可以单独写一个方法或是小类. 判断是否为空 以下是参照高晓青师傅的博客写的. ''' <summary> ''' 判断窗体上所有控件是否为空 ''' </su

CRM系统PaaS化,满足企业个性化需求

一千个人,有千种长像,一千个企业,有各种需求.有千种流程,标准模式的CRM系统很难满足个性化流程及需求的企业,那么CRM系统要Paas化,才能满足个性化需求的企业. PaaS是什么? PaaS是Platform-as-a-Service的缩写,意思是平台即服务. 把服务器平台作为一种服务提供的商业模式.通过网络进行程序提供的服务称之为SaaS(Software as a Service),而云计算时代相应的服务器平台或者开发环境作为服务进行提供就成为了PaaS(Platform as a Ser

[转] 项目管理---项目经理如何应对客户的需求变更?

项目管理---项目经理如何应对客户的需求变更? 目录(?)[+] 相信做软件开发的我们,大家都有这样的体会,当我们辛辛苦苦的熬了几个月的通宵.加班后,终于完成了客户提出的V1.0功能需求,当我们大家准备按部就班的进行系统上线时,客户.企业用户突然改变了需求,不想这么做了,提出了新的需求,新的变动,这样对于我们整个团队来说,正如晴天霹雷,很恐怖的事情啊,因为有时候,用户只是简单的一句话,但是对于系统的调整来说工作量是非常大的. 需求变更,本应是客户的权力,但也是实施顾问的为难之处.如果确需变更,当

转载--提高C++性能的编程技术

读书笔记:提高C++性能的编程技术 第1章 跟踪范例 1.1 关注点 本章引入的实际问题为:定义一个简单的Trace类,将当前函数名输出到日志文件中.Trace对象会带来一定的开销,因此在默认情况下不会开启Trace功能.问题是:怎么设计Trace类,使得在不开启Trace功能时引入的开销最小. 1.2 使用状态变量开关功能 用宏来开关Trace功能很简单,在不开启时开销完全没有: #ifdef TRACE Trace trace("aaa"); #endif 缺点是每次开关都需要重新

转载CI系统搭建二、Gitlab的安装和配置

博客转载于longgeek.com, 地址 http://longgeek.com/2013/12/26/ci-system-structures-ii-gitlab-installation/#i-4. 我在自己电脑上安装了这个gitLab,亲自测试过博客中描述的步骤很全.其中 域名如果没有替换为自己本地的地址,需要带上端口号.其中端口号为8081 上一篇文章 CI 系统搭建:一. 基础环境设置.规划 大概规划了下环境,本文主要用来记录安装 Gitlab 的过程,主要参考官方文档 并没有做太多