(转载)提高系统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抽象以应对复杂的需求的更多相关文章
- 用 ThreadPoolExecutor/ThreadPoolTaskExecutor 线程池技术提高系统吞吐量(附带线程池参数详解和使用注意事项)
1.概述 在Java中,我们一般通过集成Thread类和实现Runnnable接口,调用线程的start()方法实现线程的启动.但如果并发的数量很多,而且每个线程都是执行很短的时间便结束了,那样频繁的 ...
- (转载)提高mysql千万级大数据SQL查询优化30条经验(Mysql索引优化注意)
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...
- 【转载】图说OOP基础(一)
本文用图形化的形式描述OOP的相关知识.对OOP进行系统化的梳理,以便掌握. 涉及知识点: OOP的相关知识 OOP知识[Object-Orientation Programming 面向对象编程]总 ...
- 转载--提高C++性能的编程技术
读书笔记:提高C++性能的编程技术 第1章 跟踪范例 1.1 关注点 本章引入的实际问题为:定义一个简单的Trace类,将当前函数名输出到日志文件中.Trace对象会带来一定的开销,因此在默认情况 ...
- [转载]提高rails new时bundle install运行速度
最近在新建rails项目时,rails new老是卡在bundle install那里,少则五分钟,多则几十分.这是因为rails new时自动会运行bundle install,而bundle in ...
- 【转载】系统吞吐量(TPS)、用户并发量、性能测试概念和公式
系统吞度量要素 一个系统的吞度量(承压能力)与request对CPU的消耗.外部接口.IO等等紧密关联.单个reqeust 对CPU消耗越高,外部系统接口.IO影响速度越慢,系统吞吐能力越低,反之越高 ...
- [转载]Android系统开机画面的实现
Android系统开机画面分为下面三个阶段: 1.开机图片:Android内核是基于标准内核的,对linux比较熟悉,特别是在开发板上移植过Linux系统的人就知道在内核引导过程中会显 示出一 个小企 ...
- SQL Server实现读写分离提高系统并发
转自:http://www.canway.net/Lists/CanwayOriginalArticels/DispForm.aspx?ID=666 在一些大型的网站或者应用中,单台的SQL Serv ...
- [转载]Windows系统的错误报告保存在哪个文件夹里?
转自:http://www.xitonghe.com/jiaocheng/xp-786.html Windows系统的错误报告保存在哪个文件夹里? 发布时间:2014-10-31 20:52:20 ...
随机推荐
- swift学习 - 单例实现(singleton)
swift中实现单例的方式 class LGConfig: NSObject { static let instance = LGConfig() private override init() { ...
- PHP中array_merge函数与array+array的区别
在PHP中可以使用array_merge函数和两个数组相加array+array的方式进行数组合并,但两者效果并不相同,下面为大家介绍两者具体的使用区别. 区别如下: 当下标为数值时,array_me ...
- 009一对一 主键关联映射_单向(one-to-one)
009一对一 主键关联映射_单向(one-to-one) ² 两个对象之间是一对一的关系,如Person-IdCard(人—身份证号) ² 有两种策略可以实现一对一的关联映射 主键关联:即让两个 ...
- CMT2300 收发一体 SUB 1G 支持灵活选频
CMT2300A 是一款超低功耗,高性能,适用于各种140 至1020 MHz 无线应用的OOK,(G)FSK 射频收发器.它是CMOSTEK NextGenRFTM 射频产品线的一部分,这条产品线包 ...
- 导入数据到mysql服务器上,报错,以及停止的解决办法
两次都是因为磁盘不够,这次得以解决: =>下面是可能会用到的命令行: 1.查看磁盘占用大小: df -hl 发现: 从上面看出来,根目录 / 下的东西沾满了 /dev/sda1 但是 / ...
- Vue2.0 全家桶开发的网页应用(参照吾记APP)
github链接 借鉴吾记APP,使用 vue2.0+vue-router+vuex 为主要技术栈,elementui做为ui框架,多模块 spa 模式,webpack2.0 负责模块打包,gulp ...
- Navicat for mysql 11.1.20激活
由于最近工作太忙,一直没有机会分享自己在工作中遇到的困难,今天周日,在出去之前先分享下navicat(版本很重要navicat_trial_11.1.20.0.1449226634)激活. 刚开始我是 ...
- hdu3715
hdu3715 题意 给出一个递归的伪代码,当 x[a[dep]] + x[b[dep]] != c[dep],就向下递归,给出a,b,c数组的值 问 dep 最大多少.其中 0 <= c[i] ...
- 移动端页面开发适配 rem布局原理
主题 HTML移动端页面开发适配 rem布局原理 什么是适配,为什么要适配 我们拿到的设计图一般是以640,750,1080分辨率为基准设计的,而现在的手机终端各式各样,分辨率不同,逻辑像素不同 ,适 ...
- PHP的面向对象 — 封装、继承、多态
K在上一次的基础篇中给大家介绍了一下关于PHP中数组和字符串的使用方法等,这一次,K决定一次性大放送,给大家分享一下PHP中面向对象的三大特性:封装.继承.多态三个方面的知识. 一.封装 在PHP中, ...